Skip to content

feat(runtime): declare Collection Profile support, bindings and source identity - #635

Draft
tnunamak wants to merge 5 commits into
feat/collection-observationsfrom
feat/collection-profile-declaration
Draft

tnunamak wants to merge 5 commits into
feat/collection-observationsfrom
feat/collection-profile-declaration

Conversation

@tnunamak

@tnunamak tnunamak commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Base: #630. Merge that first, or retarget this PR to main after it lands.

What changes

Before this PR the runtime had no single place that decided whether it could honor a manifest's Collection Profile declaration. A connector could be admitted and then fail late, and a connector's source identity was inferred from its connector_id or manifest_uri.

  • One support declaration. REFERENCE_COLLECTION_PROFILE_SUPPORT (runtime/collection-profile-support.ts) lists the protocol versions and capabilities the runtime implements (OBSERVATION, STREAM_EVIDENCE) and discloses binding_authority as outstanding. The registration gate and the run path both use it. The owner session reads it at GET /_ref/collection-profile before any run.
  • Binding grammar. runtime/binding-declarations.ts parses runtime_requirements.bindings with the pinned grammar and schema (vendored byte copies, connector-binding-grammar.mjs, connector-manifest.schema.json). A duplicate object key is refused at POST /connectors and when stored manifests are read. A required binding the runtime cannot supply is refused before spawn. START lists only the bindings it supplies.
  • Capability gate. A required capability the runtime does not implement is refused before spawn. BLOB is no longer tolerated, so a manifest that declares it is refused. START no longer advertises DETAIL_COVERAGE_KEY_SETS.
  • Human interaction. An INTERACTION.kind must appear in capabilities.human_interaction. The old interactive binding is gone.
  • Typed refusal. A run refused before spawn throws an error with failure_reason: protocol_capability_unsupported instead of a plain Error. This covers an unsupported required capability, a malformed protocol_capabilities list, and an inadmissible profile declaration.
  • Source identity. A retained source identity stays fixed (server/auth.ts, connector-legacy-source-map.ts). A first registration of a key with no reviewed mapping needs an explicit source_declaration. The installer verifies the digest of the source-declaration.json sidecar. The historical first-party mappings are pinned and checked by scripts/legacy-source-binding-validation.ts in the registry generator.

Defect fixed in this PR

  • Orphan root after a crash (cbf166de52, after b01869a5ee). The installer renames a verified root into place and then writes the active record. A crash between the two left a root with no record, and every later install of that digest threw "root already exists without a matching active record". The first fix adopted such a root by hashing the files found there into a new record. That let the bytes on disk vouch for themselves, so a root altered after the crash became the verified install. The install now removes a root that has no active record and installs again from the verified artifact, as it already did for a root that failed its checks. The crash window heals without manual cleanup and nothing the crash left can become active. The crash point connector-install:after-publish stays. Tests in connector-install-registration-crash.test.ts: a retry reinstalls; a root altered after the crash (executable only; executable, provenance, a sidecar and an extra file) is replaced by the artifact's bytes. The two alteration tests failed against the adopting code. The same pattern exists on main, where a retained root of another digest is reused when the active record names a different digest (reuseExistingInstall with no record for that root); that path is not changed here.

Repeated object keys in an installed profile (e6affb6d1e)

The installer read an installed Collection Profile with JSON.parse, which keeps the last value of a repeated key. A bindings map that declared network twice reached registration as one binding. The HTTP and local-source paths already reject a repeat, so one declaration was accepted or refused depending on how it arrived. Both profile reads in connector-install/index.ts (at install and when an active record is verified) now use parseManifestJsonText on the text as written. The error says only that the profile repeats an object key, with no key or path. Tests in connector-install.test.ts: a repeat at the bindings level, a repeat inside one binding and an escaped spelling are each refused at install with nothing active, nothing registered and the root removed; a record whose profile bytes repeat a key under their own recorded hash is not runnable. All four failed before.

Fixture follow-up (68de4ccb33)

Three fixtures registered a manifest with no source declaration, which this PR's gate refuses: stream-evidence, semantic-startup-backfill-catches-crash-abandoned-record and search-index-repair-correctness.postgres. They now declare the source with withFixtureSourceDeclaration. Memory: 16 and 43 pass. PostgreSQL: 6 pass.

Checked and left alone

  • Crash between activation and registration. A startup reconcile (reconcilePolyfillManifests, wired in server/index.ts) already registers the manifest of an installed connector the registry lacks. connector-install-registration-crash.test.ts kills the child at connector-install:after-activate and asserts the registry is restored on the next start, for a first install and an update. Both pass here.
  • assertStoredFileHashes. It hashes the manifest, entrypoint and provenance, which are every file the pinned installer writes. Sibling code files appear only if a later installer version writes them, so this is not a defect today.
  • publishConnectorActivation and completeConnectorActivation. Neither has a production caller, here or on main; only removeConnectorActivation is called. This PR extends the module to carry the source identity, and tests call it. Wiring or deleting it changes the install flow, so it is not done here. Decision needed from a maintainer.

Differences from the working source

  • Group-specific hunks are left to their own PRs: the recovery-hint gate and checkpoint rules, restricted uploads, and the binding authority and browser admission work. Their test edits in the shared files are not taken (for example collection-profile.test.ts hint cases, scheduler.test.ts retry cases).
  • The binding placement plan here only decides what START lists. The binding-authority PR replaces it.
  • connector-install/index.ts does not carry the inspect hook from the browser-admission work.

Upgrade and rollback

A manifest or connector written for 0.1 can stop working under this PR. A manifest that declares an interactive binding in any form is refused before spawn, because the canonical schema has no such binding (runtime/index.ts, normalizeBindingDeclarations). A connector that emits an INTERACTION whose kind is not in capabilities.human_interaction fails the run (runtime/index.ts, "Connector emitted INTERACTION.kind not declared in capabilities.human_interaction"). The old success case is replaced in collection-profile.test.ts. This PR adds no compatibility mode for either case.

The support document and the typed refusal reason are additive. An older console shows the refusal text as it does today. There is no down-migration for the retained source identity: roll back by restoring the backup taken before the upgrade.

Verification

Run from reference-implementation/. Pass rule: exit 0 and no test:fail.

Targeted runs on the final tree, one file each (memory unless noted):

  • collection-profile: 149 pass. collection-profile-020: 21 pass, and 21 pass on PostgreSQL. scheduler: 43. event-spine: 53. run-interaction-stream-routes: 62. run-interaction-control: 12. ri-zero-connector-knowledge-conformance: 116. owner-connection-intent: 27. connector-install and connector-install-registration-crash: 64 together. postgres-template-eligibility-inventory: 3. Also green: runtime-stream-collection-failed-commit, scheduler-static-secret-injection, checkpoint-dependency-profile-conformance, auto-enroll-eligible-schedules-integration, browser-enrollment-shell-route, owner-connector-templates, composed-origin, both connector-state-scheduler-conformance files, local-source.test.ts, and the 21 files of the focused list.
  • PostgreSQL, one file each: connector-install-registry-integration, connector-install-sql-persistence, connector-source-identity-boundary (the PostgreSQL cases run; 0 fail).
  • tsc --noEmit passed before the last test edits.
  • Run at the earlier head a8093a1b12, after the rebase onto the then-current feat(runtime): accept OBSERVATION messages and keep a per-run diagnostic record #630 head (the recovery-hint gate moved to feat(runtime): stop acting on connector claims about recovery and coverage #636), memory unless noted: tsc --noEmit exit 0; collection-profile 149, collection-profile-020 24 (24 on PostgreSQL), collection-profile-binding-declarations 27, collection-profile-owner-journey 1, connector-install-source-declaration 20, connector-install-registration-crash 9, connector-install 55, connector-registry-manifest-derivation 10, runtime-observations 48, scheduler 43, detail-coverage-shortfall-severity 28, stream-evidence 16, runtime-stream-collection-failed-commit 14, checkpoint-dependency-profile-conformance 8, manifest-runtime-requirements 5, event-spine 53, cli 141, local-source 8. PostgreSQL: connector-observation-store 22, connector-install-postgres 17, connector-install-sql-persistence 2, checkpoint-dependency-profile-conformance-postgres 5, connector-source-identity-boundary 85. 0 fail in every file.
  • After the review fixes (head e6affb6d1e), 0 fail: tsc --noEmit exit 0; memory connector-install 59, connector-install-registration-crash 11, connector-install-source-declaration 20, connector-install-registry-integration 1 (1 skipped), connector-install-sqlite 1, connector-source-identity-boundary 43 (42 skipped), collection-profile-binding-declarations 27, connector-registry-manifest-derivation 10, owner-connector-templates 16, local-source 8, collection-profile-020 26, collection-profile 149; PostgreSQL connector-install-postgres 17, connector-install-sql-persistence 2, connector-install-registry-integration 2, connector-source-identity-boundary 85.
  • After the rebase onto the third-review fix in feat(runtime): accept OBSERVATION messages and keep a per-run diagnostic record #630 (head 68de4ccb33): tsc --noEmit exit 0; memory collection-profile-020 28, collection-profile-binding-declarations 27, connector-install-source-declaration 20; PostgreSQL collection-profile-020 28 and connector-observation-store 22. 0 fail.

Still owed: a full-suite run on memory and on PostgreSQL for the final head. A full memory run on an earlier head ran 11,273 tests before it was stopped for a host-load freeze; every failure it showed has been fixed and re-run file by file as listed above, but the tail of that run did not finish. The one known failure that also fails on main is connector-failure-diagnostics "stderr-bearing exit before DONE" (a Node module.register() deprecation line in the child's stderr).

Assisted-by: AI

@tnunamak
tnunamak force-pushed the feat/collection-profile-declaration branch 2 times, most recently from a8093a1 to 68de4cc Compare November 6, 2026 12:34
…d source identity

A 0.2.0 manifest declares what it needs (bindings, capabilities, human
interaction kinds) and what source it speaks for (source declaration).
The runtime had no single place that decided whether it could honor that
declaration, so a connector could be admitted and then fail late.

The runtime now:
- keeps one support declaration, `REFERENCE_COLLECTION_PROFILE_SUPPORT`,
  used by the registration gate and the run path, and serves it to the
  owner session at `GET /_ref/collection-profile` before any run;
- parses `runtime_requirements.bindings` with the pinned grammar and
  schema (browser, desktop_session, filesystem, network and namespaced
  names). A required binding it cannot supply is refused before spawn,
  and START.bindings lists only what it supplies;
- refuses a required capability it does not implement (BLOB is no longer
  tolerated), and stops advertising DETAIL_COVERAGE_KEY_SETS in START;
- requires an INTERACTION.kind to be declared in
  `capabilities.human_interaction` instead of an interactive binding;
- refuses a run before spawn with `failure_reason:
  protocol_capability_unsupported` when the declaration or a required
  capability cannot be honored;
- keeps a registered connector's source identity once retained, and
  requires a first registration of an unmapped key to carry an explicit
  source_declaration. The installer verifies the source-declaration
  sidecar digest. Historical first-party mappings are pinned and checked
  by the registry generator.

Existing tests now declare their fixture source and human interaction
kinds explicitly.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… activation

The installer publishes a verified digest root with a rename and then
writes the active record. A crash between the two left a root that
verified against the catalog entry but had no record, and every later
install of that digest threw "root already exists without a matching
active record". The owner could not recover without deleting the root by
hand.

The install now activates and registers a root that re-verifies against
the entry, the same way it reuses a root that has an active record. A
root that does not verify is still removed. A symlinked root is still
refused. A named crash point after the rename lets the kill-and-restart
test reach that window.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Three test files register a manifest with no `source_declaration` and a
key that has no reviewed source mapping. The registration gate in this
branch refuses that, so the files failed at setup, not at the behavior
they test: stream-evidence, the startup semantic backfill crash test and
the search-index repair test on PostgreSQL. Each fixture now declares its
source with the shared helper, the same way the other fixtures do.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… record instead of adopting it

The previous commit on this branch adopted a published digest root that
had no active record: it hashed the files found there into a new record
and activated it. That let the bytes on disk vouch for themselves. A root
altered between the crash and the retry (the executable, the provenance
file or the source-declaration sidecar) became the verified install, and
an altered sidecar could be recorded as the pinned declaration.

With no record there is no retained verification, so nothing in the root
can be trusted. The install now removes the leftover root and installs
again from the verified artifact, which is what it already did for a root
that failed its checks. The crash window still heals without manual
cleanup, and the crash point after the rename stays so the kill-and-restart
test reaches it.

Tests: the happy-path test is renamed to say a retry reinstalls. Two new
tests alter the root after the crash (the executable alone; the executable,
provenance, an added sidecar and an extra file) and check that the retry
installs the artifact's bytes and nothing the crash left survives. Both
failed against the adopting code.

Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…Collection Profile

An installed profile was read with JSON.parse, which keeps the last value
of a repeated key. A bindings map that declared `network` twice reached
registration as one binding with the second value. The HTTP and local
source paths already reject a repeat (parseManifestJsonText), so one
declaration was accepted or refused depending on how it arrived.

Both reads of the profile in the installer, at install time and when an
active record is verified, now use parseManifestJsonText on the text as
written. The error says only that the profile repeats an object key: the
text is not trusted and the error can reach the owner, so it carries no
key or path.

Tests: a repeat at the bindings level, a repeat nested in one binding, and
an escaped spelling of the same key are each refused at install with
nothing active, nothing registered and the root removed. A record whose
profile bytes repeat a key under their own recorded hash is not runnable.
All four failed before.

Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
@tnunamak
tnunamak force-pushed the feat/collection-profile-declaration branch from e6affb6 to a4fb70d Compare November 6, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant