OpenWOP openwop.dev

All notable changes to the OpenWOP spec corpus — v2 (the current major, spec/v2/) and v1 (the maintained parallel track through the overlap, spec/v1/) — its schemas, OpenAPI/AsyncAPI, conformance suite, and contract artifacts.

Format follows Keep a Changelog loosely. Versions are spec-corpus-wide (one date, multiple artifact updates per row); per-artifact versions live in their respective package.json / schema $id fields.

Status legend (per /governance/spec-status/):

Stable · Stabilizing · Draft · Experimental — see individual doc headers for current state. The legacy STUB / DRAFT / OUTLINE / FINAL vocabulary still appears in older releases below; both are valid in the corpus.

Package version map. The version headers below are spec-corpus versions, not package versions — each package advances independently on its own per-package release tag. So a corpus version may be skipped here when its number was already consumed by a single-package release, and not every published package version has a corpus row. Notably there is no corpus 1.1.6: that number was taken by a TypeScript-SDK-only npm patch (@openwop/openwop@1.1.6, published 2026-05-31, the agent-platform/roster helper catch-up), so the corpus skipped straight to 1.1.7. A deep-link like #v1-1-6 therefore has no anchor here by design — the TS SDK's own history lives in sdk/typescript/CHANGELOG.md and on npm. Current published per-package versions (as of the 1.1.7 corpus release): @openwop/openwop 1.2.0 (npm) · @openwop/openwop-conformance 1.18.1 (npm) · openwop-client 1.1.7 (PyPI) · github.com/openwop/openwop/sdk/go v1.1.7 (Go modules).


Unreleased · unreleased

Unreleased
  • CONTRIBUTING.md gains "An RFC MUST NOT state a rule a core doc owns" — open the owning spec/v2/core/*.md and grep conformance/src/scenarios/ before landing normative RFC prose, because openwop-check.sh cross-checks neither. Recorded with the measurement that makes it a process rule rather than a gate: three mechanical proxies were designed and tested against the corpus and all three fail — citation of the owning doc (367 findings, ~all noise), the same restricted to specifically-owned error codes (35 findings, 0 real), and "an RFC asserting a MUST must name a conformance scenario" (0 signal; all 178 Active/Accepted RFCs already comply). The third is the instructive one: the defective RFC satisfied every structural proxy — §A.4a cited events.md and named scenarios while contradicting both.
  • RFC 0180 §A.4a records the era-2 reader ruling: the refusal stands. The open question left by #1347 is answered on measurement rather than argument. A production census by the tier-1 host (277 tenant/type pairs over 50 types) put the malformed-name exposure at one row; nine of the ten pairs it had flagged as invalid turned out to be whole JSON envelopes written into the type column by an emit arity bug, fixed at source and not backfilled. The real exposure is 1273 rows under ai., node. and conversation.* — and the codemap's v1 side shows node. (10 rows) and conversation. (3 rows) are namespaces the protocol owns, so node.progress is a host-invented event inside protocol space. Passing it through would make node.startd — a one-character typo of a real protocol event — indistinguishable from it. Registration is what separates a vendor event to carry from a protocol event somebody misspelled. Relaxing the rule would also relax a MUST (COMPATIBILITY.md §2.2) and invalidate every certified host, while keeping it invalidates none. Also retracted: the claim that registration-gated refusal is incoherent because the registry ships in spec-artifacts — the codemap ships there too, so version-dependent readability is the design, not a defect.
  • RFC 0180 §A.4a's normative text is withdrawn. Landed 2026-09-13 and widened the same day, it asserted that an era-2 reader MUST accept a type the codemap does not name. spec/v2/core/persistence.md §The reader rule, spec/v2/core/events.md §Era-2 and RFC 0176 §A.3 all say the opposite — "a run whose log the host cannot translate is not readable, not 'tolerantly' readable" — and v2-unmapped-type-refused witnesses it on both halves. RFC 0176's migration row openwop.migration.C9.3 names the tolerant reader as the behaviour v2 migrates AWAY from, so the amendment silently reversed a tracked migration decision from inside a document about registration procedure. Neither revision opened the core docs or grepped the suite, and openwop-check.sh passed both times because nothing cross-checks an RFC's normative claims against the core spec. Registration binds the WRITER; the reader rule is RFC 0176's and is unchanged. The durable-log cost that prompted it is real and now recorded as an open question — 42 unnamed vendor types on the tier-1 host, 11 invalid under events.md §Types today — together with why it cannot be solved from RFC 0180: the codemap grammar admits only two kebab segments, "what the host could have written" is not resolvable from the wire, and relaxing the refusal requires an RFC 0176 amendment that moves both scenarios in the same PR. Caught because a host implemented the amendment and would have broken a certified bundle.
  • host-sample-test-seams.md §5 said the mock-AI program seam is safe to use in parallel because "each conformance scenario uses a unique fixture (and therefore unique nodeId)". The seam is keyed by nodeId alone — no run, no workflow, no tenant — so that parenthetical was the entire safety argument, and the corpus contradicted it: nine fixtures declared a node called structured-call. Vitest runs scenario files in parallel, so any two of the nine overwrote each other's program mid-run. This is the long-lived replay-observable-sequence-determinism flake — a scenario that never programs the mock, runs conformance-phase4-nondet-tool, and inherits whatever the last writer left; with a refusal staged by an envelope scenario its source run reached failed and it reported expected 'failed' to be 'completed', an assertion about replay failing for a reason unrelated to replay. Green alone, red under the gate, indifferent to load — because contention was never the variable. The nine nodes are now named for their fixtures, making §5's claim true rather than aspirational; the sentence is rewritten as a MUST that says why, and conformance/scripts/check-mock-ai-node-ids-unique.mjs enforces it. Suite cut 2.1.7; hosts re-register the nine fixtures when they pin it.
  • scripts/check-manifest-top-level-segments.mjs: adding a top-level name to spec/v2/path-manifest.json now requires updating a pin, because it silently moves versioning.md §5's retirement intersection — "manifest top-level segments ∩ anything else served unversioned" — under every host at once. The corpus owns one operand of that test and nothing checked it. The live case: packs is a declared family and not a manifest segment, so a tier-2 host's public /packs/v1/** catalog does not collide today; the day an operation lands under /packs it would, and at retirement that catalog would begin answering a protocol operation. The gate makes that day loud. A pin, not a rule — it does not say which names are allowed, only that changing the set is deliberate.
  • RFC 0180 §A.4a is widened from a SHAPE test to a reserved-prefix test. As first written it required a reader to accept an era-2 type "matching the vendor branch of the grammar" — which does not reach the rows that need it most. The tier-1 host, checking the three-segment cap against its own emitted set, found 11 of its 42 vendor types are invalid today: 7 exceed the cap and 4 contain an underscore. Those rows are durable now. Under a shape test a reader refuses them forever, converting a writer's past bug into permanent unreadability for every reader — the same harm §A.4 forecloses when it refuses to let deregistration orphan a log. §A.4a now says an era-2 type the codemap does not name is passed through opaquely whether or not it is well formed, with one exception decidable from the wire alone: an openwop.-prefixed type the codemap does not name still fails 500 event_type_unmapped, because that one claims protocol semantics the reader does not have. The host's own proposal — refuse only what could not have been written by any producer this host has run — is declined: it is answerable only from host-local history, so two conforming readers would disagree about the same row, which is the private-mapping defect persistence.md already forbids.
  • RFC 0180 gains §A.4a: an era-2 read tests the vendor shape, not the registration. events.md §Era-2's "carries no vendor org" was ambiguous between vendor-shaped and vendor-shaped-and-registered, and the readings differ on durable data — under the strict one, a host re-namespacing its vendor types would make every run it had already written unreadable. The registered-org requirement binds the writer; a reader must not retroactively invalidate a log that predates the registry. Same principle §A.4 already applies to deregistration. Raised by the tier-1 host, which declined to adopt it as a local convention.
  • conformance/scripts/generate-scenario-majors.mjs refuses to promote a scenario to majors: [1, 2] while it gates on a capability .supported seat. capabilities.md line 39 — "supported | does not exist; presence of the record is the claim" — makes that field absent by construction on a conformant major-2 host, so such a scenario does not fail there: it records inapplicable forever and the family silently stops being tested, looking exactly like a host that does not advertise it. 170 of the 443 major-1-only files gate this way; all are correct today because they only run at major 1, and promotion is the act that makes one wrong. Found by the tier-2 host session. Non-normative, generator-only.
  • docs/runbooks/V2-HOST-MIGRATION.md: the week's two instrument failures recorded as one paired lesson — a scenario that shipped but was never selected (suite 2.1.1), and a host that passed 3,158 unit tests while being unable to answer its own queued callbacks (the tier-2 retirement rehearsal). "A test suite is not a rehearsal, and a green step is not evidence that the thing was asked." Non-normative; no cut.
  • RFC 0182 Active → Accepted on tier-1 evidence: the reference host (openwop-examples cc2d181, suite 2.1.1, CI run 34558191209) advertises runList and passes v2-run-list 3/3 with 74 major-2 files selected — the first run in which the scenario was actually selected (see 2.1.1).

2.1.6

2026-09-13

a probe that asked about shape and never reached the question it asserted

Conformance-only. v2-compensation-read-projection's unknown-run leg probed /runs/conformance-no-such-run-0173/compensation — a bare run id. A bare id is the v1 spelling: identity.md §5 admits it through the overlap and requires a host advertising no 1.x member to refuse it 400 validation_error. So on a retired host the probe asked about SHAPE and never reached the existence check it asserts, failing expected 400 to be 404 against a host that was correct.

Found on the FIRST RUN of the reference host's new retirement lane (openwop-examples#44) — a CI leg that boots the host with v1 retired and runs the major-2 suite against it. Nothing else could have found it: every other host in the matrix advertises two majors, where the bare form is admitted.

Fixed

  • The probe forms a tenant-bound unknown id from a run it creates, so it

tests existence rather than spelling. A bound id answers 404 on an overlap host and on a retired one, so the fix needs no branch on the advertisement — it just stops using the one spelling that is conditional. Verified against both.

Measured and deliberately not changed

Two other major-2 scenarios probe /runs/does-not-exist: v2-error-registry (needs any >= 400 envelope — a 400 validation_error is one, and arguably a better fit for a helper named badRequest) and v2-header-scheme (collects responses to check the header scheme, any status). Neither asserts a status, so neither is wrong on a retired host. Recorded rather than changed, because rewriting a passing probe to look tidier is how a cheap check acquires a run-creation it never needed.

2.1.5

2026-09-12

two stale claims, found by opening a red nobody read

No wire change, no MUST relaxed. Cut because spec/v1/deprecations.json ships inside @openwop/spec-artifacts and its version must keep identifying its contents.

Fixed

  • Erratum spec/v1/version-negotiation.md §"The engineVersion axis is

split": the paragraph listed run-snapshot.schema.json among the string-typed schemas. That schema has been number since 2026-09-04, corrected because §Stamping's engineVersion: number MUST and a string schema were not jointly satisfiable — a host obeying the prose emitted a snapshot failing its own schema. An implementer reading the paragraph today walks back into the bind the correction removed; I nearly did, fixing the SQLite reference host. The split is three-valued in v1.x: integer at the discovery root, number on the run snapshot, string on the event log (run-event.schema.json and the runStarted / runRestoredFromSnapshot / workflowRestored payloads). spec/v1/deprecations.json's engine-version-type-split surface carried the identical stale claim and is corrected with it.

  • .github/workflows/conformance-soak.yml: the header said "Two independent

jobs" (there are three) and both it and the host-conformance job NAME advertised a three-language SDK smoke that moved to openwop-sdks — the same file says so at the point where the step used to be. A job name is read as a coverage claim; one naming a step the job does not have is the same shape as a docstring describing a check that does not exist, which is what let the eventLogSchemaVersion MUST go unasserted across 444 scenario files.

2.1.4

2026-09-12

a replay-determinism red that could not be diagnosed

Conformance-only; no normative prose changed. A tier-1 host carried expected 'failed' to be 'completed' from replay-observable-sequence-determinism across three lanes — green standalone, red in the full gate on byte-identical code, with load falling during the red run — and could not diagnose it once in three attempts, because the suite threw away everything that would have explained it.

expect(sourceTerminal.status).toBe('completed') discards the run's error object (schemas/run-snapshot.schema.json carries error and currentNodeId). Every occurrence reported that something went wrong and nothing about what. That is the suite owing hosts what RFC 0064 §F makes a host owe the wire: a failure MUST be self-describing.

This does NOT fix the flake — the cause remains unknown, and saying otherwise would be the week's fourth instrument answering a narrower question than it appears to. It makes the next occurrence carry its own evidence.

Fixed

  • Both replay scenarios (replay-observable-sequence-determinism,

replay-divergence-at-refusal) report the terminal state with currentNodeId and error.{code,message,nodeId,retriable} when a run does not reach completed, and say so explicitly when a non-completed terminal arrives with no error object at all.

  • The same two files were the only scenarios in the suite that rolled their own

poll loop and ignored OPENWOP_POLL_TIMEOUT_SCALE — the knob lib/polling.ts exists to provide, whose own docblock names this failure: "observe no change in those scenarios, and record a failure that measured the environment rather than the host." Their 5s bound now scales like every other, and the timeout message prints the bound and the scale it used.

2.1.3

2026-09-11

what `since` names in a capability record

Two hosts read the capability record's since differently on the same day and the corpus could not settle it: core/capabilities.md §2 gives the field a <major>.<minor> grammar and never says whose version it is. It is the host's — the minor of this host's contract at which it began serving the family, a point on its own protocolVersions[] timeline, not the corpus minor that introduced the family.

The reasoning, recorded because the field is small and the ambiguity was not: spec/v2/declaration.json carries witness and maturity per family and no since, so there is no corpus value to copy — witness is the record's one corpus-derived field. status/until absorb v1 tier/experimentalUntil (spec/v1/capabilities.md §"Capability stability tier"), which is explicitly the host's own stability claim with a sunset rule binding the host. A since on the corpus timeline beside an until on the host's, in adjacent fields sharing the axis-1 grammar, is incoherent; a since naming a minor absent from protocolVersions[] is the symptom.

Changed

  • scripts/generate-from-declaration.mjs gives the generated since property

a description stating this, so it reaches every implementer who opens schemas/v2/capabilities.schema.json (72 family records). Recorded in the generated schema rather than in prose: spec/v2/core/ stands at 24,998 of its 25,000-word budget, and a machine-readable annotation is in front of the reader who needs it.

2.1.2

2026-09-11

a conformant retired host failed a suite leg for being conformant

Conformance-only; no normative prose changed. v2-version-header-honored applied its two-major byte-comparison to every host. On a host with no 1.x member the header-less request is served major 2 itself (versioning.md §1.3 row 3 with §1.1 — preferredVersion MUST equal the single major served, RFC 0179 §A.1), so asking for 2.0 names the same contract as asking for nothing and identical bytes are the only conformant answer. The leg called that "the header was IGNORED … the host served v1 and called it v2" on a host holding no v1 at all. Every host retires into that state; it survived because none had ever been measured there. Reported by the tier-2 host from a throwaway v1-retired lane and reproduced here against a stub implementing §1.3's table.

Fixed

  • v2-version-header-honored leg 1 branches on the advertisement. Overlap:

unchanged. No 1.x member: probe the rule that still discriminates — a major not in protocolVersions[] MUST be 406 protocol_version_unsupported with root-level details.protocolVersions[] echoing the list (§1.3 row 2), using 1 when it is the unserved major, else 9. inapplicable was the wrong repair: on a retired host v2-dual-stack-negotiation gates itself off for want of a second major and takes its unlisted-major probe with it, so row 2 would have been measured by nothing. Sabotage-proved in four states — conformant single-major (pass), single-major ignoring the header (fail), real dual-stack reference host (pass, unchanged), dual-stack ignoring the header (fail, the original 2026-09-04 defect).

  • The scenario's docblock always stated both rules; only the two-major one was

ever implemented.

Added

  • docs/runbooks/V2-HOST-MIGRATION.md "Phase 6 — Retirement, rehearsed": the

atomic flag, the self-addressed-URL inventory (a host's own task-queue callbacks under /v1 retire with it — 22 blocked "run did not settle" that 3,158 unit tests could not see), the drain window for queued work, §5's unversioned-collision test, and the 2.1.2 pin floor for a retired cut. versioning.md §5 already answers where those callbacks belong (/host/<org>/…, RFC 0181), so no normative sentence was spent.

2.1.1

2026-09-11

the run-list scenario was never selected

Conformance-only. Suite 2.1.0 shipped v2-run-list.test.ts in the package but not in conformance/scenario-majors.json, the registry --target-major selects from; every 2.1.0 run reported 73 major-2 files and the scenario never executed — the reference host's 260/260 green (openwop-examples #39) was a green step that never asked the thing it reported on. RFC 0182's acceptance criterion is therefore still unwitnessed and the RFC stays Active.

Fixed

  • conformance/scenario-majors.json regenerated: 517 files, 74 targeting

major 2 (v2-run-list included). First real run against the reference host: 3/3 legs pass.

  • scripts/openwop-check.sh now runs generate-scenario-majors.mjs --check

(the generator's docblock promised the registry "cannot drift from the tree", but nothing in the gate invoked it). A scenario added without a registry row now fails the corpus gate.

2.1.0

2026-09-11

a portable run list (RFC 0182)

First 2.x minor: one new optional operation. Normative-additive; v1 untouched; comment window waived by the sole steward and logged in the RFC. Suite 2.0.13 (the off-process webhook-receiver fix, #1312) was cut in conformance/ only and never tagged; 2.1.0 carries it.

Added

  • RFC 0182 — listRuns (Active). GET /runs returns { runs: RunSnapshot[], nextCursor? } — the caller's runs, newest first, tenant-scoped by construction with every runId bound (identity.md §5); limit honoured up to the advertised runList.maxPageSize; opaque cursor, a cursor the host did not mint is 400 validation_error; workflowId and status exact-match filters when runList.filters names them. Gated on the new core family runList (facets maxPageSize, filters; 404 not_found when unadvertised). New schemas/v2/run-list-response.schema.json; runs.md §Surface row + §List; capabilities.md § runList; derive-v2-api.py emits the operation; path manifest gains its 52nd operation.
  • Conformance v2-run-list (gated on runList, three legs): the two runs the caller just created appear and every id carries the caller's tenant segment; no page exceeds maxPageSize; a foreign cursor is refused; a workflowId filter is exact when advertised.

Changed

  • RFC 0181 Active → Accepted (2026-09-11). Evidence tier: tier-2 — steward-affiliated sibling host: MyndHyve serves /host/myndhyve/… in production and advertises myndhyve.vendor-namespace = { root, twin, roots[], rfc }; verified on the wire by the steward. §Unresolved-1 corrected: GET /runs list and DELETE /runs/{runId} were never v1 protocol operations (only createRun/getRun exist on those paths) — they are host extensions and fall under §A.1; a portable run list is worth its own additive RFC, a protocol delete would be an erasure-tombstone RFC. §Unresolved-2 has two data points (root, twin, rfc common to both hosts' adverts). No spec, schema or suite change.

Why now

The question arrived as "should GET /runs join the manifest before December" and turned out to be misfiled: it was never a v1 protocol operation (RFC 0181 §Unresolved-1, corrected). What remained was the interop gap — every UI grows its own list — and the corpus already had every primitive the portable one needs.

2.0.12

2026-09-10

host-proprietary paths have a home: `/host/<org>/…` (RFC 0181)

The gap versioning.md §5 recorded on 09-09 as undecided-not-permissive is decided, after two production hosts measured it the same day (1,062 and 11 proprietary roots, and one rewrite façade already answering /host/<org>/… under major 2 by accident). Normative-additive; v1 untouched; comment window waived by the sole steward and logged in the RFC.

Added

  • RFC 0181 — vendor path namespace (Active). A host MAY serve operations the manifest does not name at /host/<org>/… for an org registered in spec/v2/declaration.json extensions; no major in the path; never a protocol operation, never measured; advertised under extensions.<org>.<name>; a /v1/host/<org>/… twin MAY ride the overlap and retires atomically. /v2/host/… and a /v1/host/* retirement exemption are rejected with their clauses.
  • spec/v2/declaration.json — orgs openwop-app and myndhyve registered (both hosts already emit vendor codes and event types under those names; only example was registered); reservedOrgs gains every manifest segment under /host/ (effect-seams, events), which the org grammar previously admitted.

Changed

  • versioning.md §5 — the open-gap paragraph becomes the decision. §1.4 — the non-protocol-response constraints (no OpenWOP-Version, not application/json) are scoped to a manifest-named path, which is what the suite reads; read literally they forbade JSON on a vendor path.

Clarified, no change

  • persistence.md §"The v1 wire of an era-3 log" already states the inverse-codemap MUST a host reported as a gap; cited on the bus rather than restated.

2.0.11

2026-09-10

the emitter scrubbed the key id it was required to publish

Conformance-only. No spec, schema, or scenario-logic change; the pinned @openwop/spec-artifacts peer moves with the suite version.

Fixed

  • openwop-app's major-2 bundle re-cut on suite 2.0.11 — 0 executed-fail, 3 blocked. evidence/v2-host-bundles/openwop-workflow-engine.json: app.openwop.dev at f7c631b9b (serving openwop-app-backend-00688-9bk), suite 2.0.11, signed openwop-app-self-2026-09-04, through the production front door: 137 executed-pass / 0 executed-fail / 3 blocked / 87 inapplicable over 227 rows, 1273 assertions (rc.56 cut: 125 / 2 / 29 / 66). Both v2-poll-cursor-v2 fails closed (ADR 0633); the 25 seams-gated blocked rows are inapplicable on 2.0.10+ (profile not advertised, honestly); the new openwop.requirement.0170.id-grammar.bare-id row passes on the overlap branch. The 3 blocked rows are the SSRF loopback receiver, lane-side. check-cut-gates.mjs --host-bundle --network: 9/10 PASS, Front door PASS (was FAIL); Coexistence FAIL only on the seams-gated legs. openwop-core-standard witnessCount 12 (was not claimed). Not certified — blocked > 0 (RFC 0168 §E.1). Anchor unchanged (2026-09-05, 223c0edc).
  • --certify (v3 bundles, suites 2.0.8–2.0.10): evidenceSecretsFromEnv classified OPENWOP_BUNDLE_SIGNING_KEY_ID as a secret because its name matches KEY, and redacted the published keyId out of the embedded discovery.document after discovery.sha256 was taken — so every host that passed the key id by environment failed its own bundle's self-verification. Found by MyndHyve across three cuts; the same host passed the day before with the id on the flag. Names ending in _ID are now excluded, the emitter names the keyId it publishes as except, and a redaction leg pins both.
  • On a self-verification failure the emitter now writes the rejected bundle to <out>.rejected.json (marked not-a-certification-artifact) instead of discarding the only diagnostic.

2.0.10

2026-09-10

three things hosts found on the day 2.0.9 shipped

Errata found by two tier-1 hosts within hours of the v2.0.9 tag, all three the same family as 2.0.9's: a v1 spelling or an unstated assumption carried into the v2 tree where no instrument read it. No wire shape, field, or error code changes; one MUST is stated that the schemas already implied; the suite gains one leg. preferredVersion stays on 1.x through the overlap.

Fixed

  • schemas/v2/capabilities.schema.jsonprompts.renderEndpoint said "Defaults to /v1/prompts:render" inside the v2 schema. The default under major 2 is the manifest key /prompts:render (versioning.md §1.2); a host that advertised /v1/prompts:render in its major-2 document was quoting the schema. Found by MyndHyve.

Changed

  • identity.md §5 — states two things the instruments had measured without prose: a tenant-bound id is one percent-encoded path segment (tenant%2Fopaque, the bound form MUST be accepted); and through the overlap the bare form (opaque only, the v1 spelling) is admitted on a major-2 path parameter, resolved under the caller's tenant only and named bound in the response — the affordance v2-dual-stack-negotiation has required since rc.44. It expires with the overlap: a host advertising no 1.x member MUST refuse the bare form 400 validation_error. The grammar for ids in documents is unchanged. A same-day steward ruling that said "refuse now" was withdrawn against those witnesses before release; a client that binds at its request seam (as openwop-app now does) is correct either way.
  • capabilities.md § a2a / § mcp — a facet MAY name a URL on another origin; that is a claim about the facet, not that the origin speaks v2; certification is per origin. Open gap recorded for the withdrawal question. Found by MyndHyve (facets pointing at a service never in v2 scope).

Added

  • Conformance v2-id-grammar — a fourth leg sends the run this caller just created with its tenant segment stripped: through the overlap it requires 200 with the bound id in the body; after it (no 1.x advertised) it requires 400 validation_error. The branch is decided from live discovery, so the leg witnesses both the affordance and its expiry.

2.0.9

2026-09-10

the corpus says v2, and a MUST that bound more path space than any instrument measures

Spec errata plus a status correction. No wire shape, field, error code, or schema change; no RFC. One normative clause is narrowed to the scope its only instrument has always measured, two things the overlap left implicit are written down, and every surface that still described v2 as unreleased is corrected. No host's disposition changes, and nothing about the overlap changes: preferredVersion stays on 1.x by MUST until v1 end-of-support.

Second erratum, same day, same defect shape: versioning.md §1.4 read "a response on any path MUST carry OpenWOP-Version" — a quantifier over the origin where the rationale (a silent downgrade) and the only instrument (v2-advertised-path-space-served's reachedUnderMajor2) both quantify over protocol operations. Narrowed to every protocol response; a non-protocol response on a shared name MUST NOT carry the header and MUST NOT be application/json; content negotiation on Accept is permitted under three conditions (protocol-client default gets the wire; the page is distinguishable; Vary: Accept, OpenWOP-Version). §5 names the retirement hazard a tier-1 host surfaced — the header-less default is what separates a page from the wire on a shared name, and end-of-support flips it — with the test manifest top-level segments ∩ anything else served unversioned. README "Published artifacts" now names the 2.0.0 SDKs the registries serve.

Changed

  • The corpus now says what major it is on. v2.0.0 was tagged 2026-09-05,

yet spec/v2/README.md still opened with "in construction … until the v2.0.0-rc.1 corpus tag", all twenty spec/v2/core/*.md carried Status: Draft · v2.0.0-rc, the root README.md led with "v1.x published artifacts" and described the v2 tree as gated on a tag that had landed a week earlier, CHANGELOG.md was titled "Spec v1", and INTEROP-MATRIX.md headed its v2 table "release candidate". A reader who trusted those lines concluded v2 did not exist — one did, and said so. Each now states the released major, the corpus version, and the file that carries the truth (spec/v2/release.json); the twenty core documents are Status: Stable · v2.0.9. The v1 tree is described everywhere as what it is: the maintained parallel track through the overlap.

  • docs/migration/v1-to-v2.md gains a client section. The guide was

written for hosts. A client's migration is three changes and one non-change, and the same reader got two backwards: OpenWOP-Version is MAY on a request and MUST on a response (not the reverse), and the 18-month end-of-support clock is conditional on a non-steward host in the matrix at release, not guaranteed. Both are now stated with the rule each comes from, alongside unversioned paths and principal/principalKindowner.subject.

Fixed

  • **versioning.md §1.2 bound more path space than any instrument measures, and

more than conformance.md permits. The advertised-path-space MUST quantified over "every operation it serves", unqualified. That is not jointly satisfiable with conformance.md §"Test seams": the seams profile mounts the real path space /conformance/seams/… while that same document requires spec/v2/path-manifest.json and api/v2/openapi.yaml to contain no seam operation — so a host serving seams under one major owed them under the other, measured against a manifest forbidden to name them. The MUST is now scoped to operations named in the manifest**, which is what v2-advertised-path-space-served has always probed and what the seams carve-out requires. The narrowing keeps the case the rule exists for: the motivating defect was POST /webhooks answering 404 under major 2 while POST /v1/webhooks answered 201, and webhooks is a manifest operation. Raised by a tier-2 host asking whether its four proprietary /v1 roots were bound; they are not, and §5 now says so rather than leaving it to be inferred.

  • §1.2 described a path manifest that does not exist. It claimed the generated

artifact carries a resolvedPath with "exactly one version segment for v1 rows and none for v2 rows". spec/v2/path-manifest.json has no resolvedPath field (rows are method / path / operationId) and no /v1 rows at all — 0 of 51, as the generator's own $comment says: "every path unversioned". The /v1 twin is derived by prefixing, which is what the conformance pairing does.

Added

  • versioning.md §5 records two things the overlap left implicit. That v1

retirement is atomic — §1.1 forbids a 2.x preferredVersion while any 1.x is advertised, so flipping preferredVersion ahead of the drop is the same step, not a smaller first one. And that host-proprietary paths have no defined successor under major 2: the corpus reserves a vendor namespace for capability records, error codes, event types and pack-document properties, each keyed to an org registered in spec/v2/declaration.json, and has no equivalent for paths. Recorded as an open gap — undecided, not permissive — rather than answered by invention.

2.0.8

2026-09-07

the verifier asked a v1 question about v2 hosts

Conformance-suite fix. No wire shape, field, error code, MUST, or prose change; no RFC. The corpus data is untouched — the defect was entirely in the suite's own verifier.

Fixed

  • A major-2 certification bundle could not verify its own profile claims.

profileDerivable had no notion of a target major and always answered from the v1 catalog, where isCore requires a scalar protocolVersion of major 1 plus supportedEnvelopes / schemaVersions / limits. A v2 declaration (RFC 0169 §C.1) carries protocolVersions / preferredVersion and family records instead, so the verifier refused every correct major-2 bundle with profile-not-derivable — "the host does not advertise it", about a host advertising exactly it. The emitter had used the right predicate all along; the two are now one function (conformance/src/lib/v2-profiles.ts) called by both. Reported by myndhyve-1 and corroborated by openwop-app-1; neither host had anything to fix. See conformance/CHANGELOG.md for the full account, including why an honesty fix in 2.0.5 is what made a long-standing defect reachable, and which population is actually exposed.

2.0.7

2026-09-06

three claims of coverage that were not coverage

Process and prose. No wire shape, field, error code, or MUST changes; the one code change is a conformance-suite disposition.

Added

  • RFC 0180 — vendor-org registration procedure. spec/v2/declaration.json

extensions is the registry persistence.md §"The codemap is data" makes a host's read behaviour turn on, and it shipped at 2.0.0 holding exactly one reserved org with no procedure anywhere in the corpus for adding a second. A live host carrying 31 types under its own org read the rule correctly and concluded its own types must be refused — correctly, and permanently, because the predicate was well defined and the procedure behind it was empty.

The RFC decides three things it could not inherit. The corpus is the sole registrar — a host-declared registry would make the refusal predicate host-controlled, which is the private mapping persistence.md already forbids. Registration takes effect on the @openwop/spec-artifacts release that carries it, not on merge, because the registry is shipped data. And a shipped entry is append-only: deregistering an org does not merely stop new types from passing, it flips every log already written under that org from pass-through to refusal, retroactively, on the next read. A log that was readable becomes unreadable without a byte of it changing. No deprecation window fixes that, which is why there is no removal path rather than a slow one. Additive per COMPATIBILITY.md §2.1; the entry shape and key grammar were already normative in declaration.schema.json and are untouched.

Closes RFC 0169 §Unresolved-1 in favour of the short org form. The deciding argument is not brevity: the dot is the type separator, so a reverse-DNS org makes the first segment ambiguous with the type path and a reader cannot split a type without already knowing the org list. The short form keeps orgOf(type) = type.split('.')[0] decidable without the registry, so an unregistered org stays identifiable and is merely not admitted.

  • spec/v2/core/conformance.md §"Whose fact is the reason?" — a soft-skip

reason MUST name a fact about the host under test; where the predicate is a fact about the suite, the row MUST record blocked, never inapplicable. inapplicable certifies and blocked is bundle-wide fatal, so the wrong disposition was also the silent one. The failure this prevents is invisible at the disposition layer: a row already inapplicable for a true host reason, re-gated onto a suite-side precondition, stays inapplicable — no count moves, no gate reddens, and the row silently stops describing the host it names.

Fixed

  • spec/v2/core/persistence.md §"The seat" claimed the

v2-v1-events-translated scenario's three readers meant "a wrapper-only adapter is caught". Three wrappers pass those three legs exactly as one correctly seated adapter does, and the rule binds every reader. The seat is a claims-check discharged by ADR disclosure and audit. The clause also named the scenario wrongly and cited conformance.md, which contained nothing on the subject. Both MUSTs stand; only the false coverage claim is removed.

  • Conformance suite 2.0.7vendorControlGate records blocked rather

than inapplicable for an unresolvable registry. See conformance/CHANGELOG.md.

2.0.6

2026-09-06

the release that made a rule uncheckable

2.0.5 shipped four fixes for rules whose instruments could not answer. One of those fixes did the same thing to a fifth: v2-unmapped-type-refused stopped running on every consumer of the published package, and reported green.

Reported by a host operator (openwop-app) who pinned 2.0.5, saw the lane go green and the ratchet call the scenario STALE, and re-measured against a local witness for a defect they knew was unfixed rather than accept a result they wanted. Their host still answered 200 to foo.bar. Two defects, one symptom:

Fixed

  • The corpus resolver was anchored on a repo-only directory.

registeredOrgs() and codemapV1toV2() located spec/v2/ as V1_DIR/../v2 — a v1-anchored path to a v2 file. spec/v1/ ships in no package, so V1_DIR is null in every published install and both lookups returned nothing. The data was never missing: it sits in the exact-pinned @openwop/spec-artifacts peer. Only the anchor was wrong. Both now resolve through SPEC_V2_DIR (lib/paths), anchored on the contract root — the one anchor that holds in a repo checkout and an install alike. The codemap failed more quietly: it fell back to 7 hard-coded rows instead of 118, so era-2 readers asserted against names a truncated map never had.

  • A precondition written for one leg gated the other.

v2-unmapped-type-refused drives two opposite halves of the reader rule — a refusal (org NOT registered) and a control (org registered). Both were gated on one preconditions() that demanded a resolvable registry, which only the control leg needs. With the registry unreachable the refusal leg soft-skipped inapplicable on exactly the hosts it exists to catch: a host answering 200 and a host answering 500 were both green. The gates are now separate and pure (unmappedRefusalGate / vendorControlGate in lib/era2-seed), with the fail-closed reading made explicit — an unreadable registry registers nothing, so the refusal is still required and the leg stays drivable.

Added

  • src/lib/era2-unmapped-gates.test.ts — the gates' truth table, including the

rows a scenario cannot check about itself: a scenario runs against a live host in whatever layout it finds and can never place itself in the layout where its own gate misfires. Sabotage-verified: restoring the 2.0.5 over-gate reddens exactly one row.

  • verify-installable now reads the corpus through the installed resolver

before declaring success. A clean npm install was never evidence the suite works — 2.0.5 passed that job while its registry lookup returned nothing for every consumer. Installs is not works.

The lesson, in the reporter's words: a ratchet's STALE signal means the scenario stopped failing. It is not evidence the defect is fixed.

2.0.5

2026-09-06

four rules with no way to be checked

Every item here is the same defect wearing a different coat: a normative rule whose instrument could not, even in principle, return the answer it appeared to give. None of the four announced itself — three of them were green.

  • The vendor-org registry three rules cite did not exist. events.md

§Rules, RFC 0171 §A.1 and persistence.md §The codemap is data all require a vendor event type's first segment to be an org "registered under extensions in spec/v2/declaration.json". The file had no extensions key, and its schema is additionalProperties: false, so it could not have arrived by accident. Three near misses kept it plausible: extensionsKeyPattern is a key SHAPE, reservedOrgs lists FORBIDDEN orgs, and metadata[] does carry a row keyed extensions — the host's own extension map in the discovery payload, a different thing under the same name. New: extensions, required in the schema, holding example (reserved: true, never assignable to a vendor, RFC 2606 precedent). A registry that can only ever refuse is not a registry.

  • persistence.md cited RFC 0171 §A.2 for the reserved-prefix rule. §A.2 is

the NAMING rule; the reserved-prefix rule is §A.1. The sentence a certifying host builds its era-2 reader on pointed at the wrong section. Repointed, and the definition of "vendor-prefixed" is now written out rather than left to the citation.

  • v2-unmapped-type-refused drove only the refusal half. A host that

refuses EVERY type the codemap does not name — including a registered vendor type it MUST pass through — went green while violating the rule. openwop.requirement.0176.vendor-type-passthrough is the control: same log shape, one segment changed. Its preconditions were a note inside a seeded payload; they are now checked against the live declaration and codemap.

  • "Byte-equivalent" was witnessed by comparing two of ten fields.

replay.md §Byte-equivalence binds [0, fromSeq); the only witness compared ${sequence}:${type}. v2-run-fork-prefix now compares type, nodeId and payload, excluding eventId/runId/timestamp/causationId — the exclusion set is runs.md §Diff and ancestry's, not one invented for the leg. schemaVersion, engineVersion and line 37's snapshot claim are recorded as named residue, unasserted, with the reason.

  • A v3 bundle's certified: true was uncheckable by its recipient. v2

bundles carried discovery.document and the verifier re-derived every claimed profile from it (RFC 0148 §B(1)); v3 shipped {url, sha256, protocolVersions, preferredVersion} and verifyBundleV3 stopped reading discovery at all. A digest of a document you do not have proves nothing. New: optional discovery.document — the digest is re-derived first (the signature covers sha256, not the document, so a swap would otherwise verify), then every certified profile is re-derived; absence is surfaced as derivabilityChecked: false rather than swallowed. check-cut-gates.mjs prefers the bundle's attested copy over the unsigned --host-discovery file.

  • A requirement's id depended on whether it passed. The registry generator

read req()'s first argument only as a string literal, so the idiomatic const ID = … recorded explicitId: null and the row fell back to a title-derived slug. v2-run-fork-prefix is in the evidence tree under two ids: the explicit one where the assertion ran, the slug where the leg was inapplicable. A verifier asking whether a bundle carries a requirement got a well-formed NO from a host that had merely not held the profile. Const bindings now resolve: 2058 explicit ids became 2106.

  • @openwop/spec-artifacts shipped 35 JSON files and no prose while

package.json files advertised spec. The codemap persistence.md calls "the only authority" shipped; the sentences saying how to read it did not. 38 markdown files under spec/v2/ now ship. spec/v1/ stays out: it is frozen.

  • memory-attribution-replay-stable is majors: [1, 2] (was [1]). Its

rule is replay.md §Determinism caveat 5 in both majors, word for word; what held it back was a gate that returned early unrecorded (indistinguishable in a bundle from an unadvertised family, an unreadable shape, or a file never selected) and three hard-coded /v1/ paths that are not seams, so the driver's rewrite never touched them. Both are properties of the instrument. The first row of the 445-file backfill; the value is the pattern.

2.0.4

2026-09-06

a fixture that punished a host for obeying the rule the scenario checks

v2-provider-conflict drove the connection-pack-github fixture, and on a host that ships a built-in github its qualified-form leg could never pass. RFC 0177 §D.1 says the later registration of a bare provider id MUST be refused — so the fixture did not install, and leg 2 recorded blocked, permanently, on a host whose only fault was obeying the rule the scenario exists to check. A production host carried that row across a dozen cuts, and a bundle with any blocked row does not certify (RFC 0168 §E.1).

  • New fixture connection-pack-acme-widgets, same shape over a deliberately fictional provider id. Fictional is the point: no host ships it built-in, so the fixture always installs and both legs are reachable everywhere. v2-provider-conflict now drives it.
  • The v1 scenario keeps the github fixture, and that was checked rather than assumed. connection-provider-resolution is majors [1], and v1 settles a collision with a built-in by version precedence (spec/v1/connection-packs.md:89) rather than refusing the install — so a host shipping a built-in github still installs the pack there. The trap is specific to the v2 rule, and the fix is scoped to the v2 scenario.
  • The built-in branch in leg 1 is kept: a host that somehow ships this id still witnesses the rule on the first install. It costs nothing and means the scenario does not depend on the fictional id staying unclaimed.

The pattern, for the fourth time this cycle. 2.0.2 blamed a host for the suite's timeout; 2.0.3 selected on a flag the obligation does not have and enforced a deadline it does not set; this one demanded that a host not implement §D.1 in order to demonstrate §D.1. An instrument must not require a host to violate the spec in order to be measured.

2.0.3

2026-09-06

a selector that asked the wrong question, and a window that convicted a host for its own width

Two scenario defects, both found by hosts reading the suite rather than running it.

  • v2-effect-seam-no-refire selected on a BRANCH permission to witness a REPLAY obligation. The target filter was r.guarded === true && r.branchReFires === false. replay.md:78 forbids exactly that conflation in one sentence: "A host MAY suppress branch effects and MUST NOT report that as replay suppression." §Suppression rule 1 is unconditional — "a node that performs an external side effect … MUST NOT perform it" — and does not vary with branchReFires, whose own schema description reads "RFC 0140 §G6 — a branch fork re-fires this seam by design". Every guarded: true row is a valid target; the filter now selects on guarded alone. Underneath it sat a second bug: branchReFires is optional (required: ["seam","kind","guarded","guardedBy"]), so === false also excluded every row merely silent on the permission. Found by the reference host, which holds ten honest branchReFires: true rows — every seam it owns genuinely re-fires on a branch — and was about to build fireEffectSeam for a scenario that would have gone on recording inapplicable after the work landed. It asked rather than inventing a false row, which would have made the scenario pass by lying about the seam.
  • v2-webhook-durable-delivery's at-least-once leg convicted a host for the suite's own window — the third time this file has had to learn it. The receiver answers 204 only on attempt FAIL_FIRST + 1, so reaching it costs the sum of the first FAIL_FIRST backoff intervals, not the largest one: on an exponential-from-30s policy, 30 + 60 = 90 s against a RETRY_WAIT_CAP_MS of exactly 90_000. A host loses by the width of one delivery. The obvious fix cannot be built: spec/v2/facets/webhooks.schema.json retryPolicy is additionalProperties: false over exactly { maxAttempts, backoff } — the base interval is not on the wire, so the suite cannot compute the time to the Nth attempt, and any cap is 2.0.1's deadline with a larger literal. The leg now records blocked with the arithmetic in its reason: the obligation was taken on and the suite could not measure the outcome (RFC 0148 §A), rather than inapplicable, which would claim it was never taken on.

The cost of the second fix, stated rather than hidden — and stated precisely, because the first version of this paragraph overstated it. It said "a host that retries forever and never succeeds now records blocked instead of failing". That is wrong, and the host affected by the change is the one who corrected it. What survives is more than that sentence implied:

behaviourstill caught?
never retries at allyesattempts.length > 1, both legs, unconditional
retries unboundedly past its advertised budgetyes — the dead-letter leg's attempts.length <= maxAttempts, whenever maxAttempts is advertised
retries within budget and never lands a successno — this is the detection actually lost
retries unboundedly while advertising no maxAttemptsno, and not newly: that leg was already gated on an advertised policy

So the loss is one row of that table, not "non-delivery". It is still a real loss — a host that exhausts honestly and never delivers now reads blocked — and it returns by putting the interval on the wire so the sum is derivable: additive retryPolicy surface, an RFC and a 2.1.0, not a patch. The trade is recorded at the assertion as well as here, and the table is the version to trust.

Where this correction lives, and why not everywhere. conformance/CHANGELOG.md ships inside the published tarball, so editing 2.0.3's entry there after @openwop/openwop-conformance@2.0.3 was published would leave the tree claiming a version whose bytes differ from npm's — check-published-suite-identity refused the commit that tried, correctly. The packaged entry therefore keeps its original wording and this file carries the correction; the shipped text is superseded here rather than rewritten in place. A published artifact is evidence, and evidence is not edited after the fact.

What both defects have in common, and it is the night's pattern one turn further out: an instrument encoded a condition its obligation does not have. One selected on a flag the requirement never mentions; the other enforced a deadline the requirement never sets. Neither was visible from the operation description a host implements against — which is why api/seams-v2.yaml's fireEffectSeam now says the scenario picks its target from the manifest.

2.0.2

2026-09-06

2.0.1's own fix could not run: the derived wait exceeded the harness timeout that governs it

A regression in 2.0.1, measured by the host that reported the defect 2.0.1 fixed. Re-cut on 2.0.1 (00337-dgw, 1444 assertions, corpus stamp verified), one row moved and it moved the wrong way: 0173.webhook-durable-delivery.dead-letter went executed-passexecuted-fail.

  • The wait was raised above the limit that enforces it. 2.0.1 widened the retry wait to a 90 s cap (RETRY_WAIT_CAP_MS) and left both it() blocks on the harness default — conformance/vitest.config.ts testTimeout: 30_000, no per-test override. A wait longer than the timeout governing it can never elapse: the test dies at 30 s with "Test timed out in 30000ms". Worst case is worse than one wait — the dead-letter leg runs waitTerminal plus two sequential retryWaitMs waits, so it needed up to 191 s of a 30 s budget.
  • It took a passing row with it. dead-letter passed on 2.0.0-rc.67 by observing attempts.length > 1 inside the old 20 s window. 2.0.1 moved that leg onto retryWaitMs too, so it now times out before reaching the observation it used to make. The fix for one row broke a second.
  • The fix derives the budget from the same constant rather than writing a second literal. RETRY_TEST_TIMEOUT_MS = RETRY_WAIT_CAP_MS + WAIT_SLACK_MS and DEAD_LETTER_TEST_TIMEOUT_MS = RETRY_WAIT_CAP_MS * 2 + WAIT_SLACK_MS, passed as each it()'s timeout. A later change to the wait now carries its own budget — the same construction as sourcing an advertised policy from the constant the delivery loop reads, so the two cannot drift.

The shape, because it is the defect 2.0.1 fixed displaced by one layer. 2.0.1 stopped the scenario blaming a host for a deadline the scenario chose — and then let the harness choose a shorter one silently, on exactly the durable hosts the widening was written to help. A guard defeated by a second guard neither party was looking at. A scenario must not blame a host for a deadline the scenario chose, and must not adopt a wait its own harness will not allow it to finish.

2.0.1

2026-09-05

the webhook durability scenario read the wrong carrier and imposed its own deadline

Two defects in v2-webhook-durable-delivery, both found by myndhyve running against 2.0.0 — the first host feedback on the released major, arriving within hours of the cut.

  • It read the v1 carrier and called it the only v2 one. advertisedRetryPolicy() read triggerBridge.retryPolicy, and its docstring asserted that was "the only v2 carrier". spec/v2/facets/webhooks.schema.json states the opposite in as many words: "retryPolicy is the v2 carrier of the delivery obligation (was triggerBridge.retryPolicy at v1)", with the field's own description adding "The webhooks family carries it at v2; triggerBridge.retryPolicy is the v1 carrier and stays through the overlap." So a host correctly advertising the v2 carrier had its policy read as null, and one still on the v1 field was measured against a different subsystem's budget. The reporting host advertises maxAttempts: 8 for its trigger-bridge state machine and enforces 5 on webhook delivery — honest about each, unable to be honest about both through one borrowed field. Now reads webhooks.retryPolicy first, falling back to triggerBridge.retryPolicy for the overlap the schema preserves. The host asked whether the webhook surface needed its own carrier; it already had one, and the suite was not reading it.
  • It imposed a 20-second deadline and blamed the host for missing it. A host whose first backoff is slower than 20 s was recorded executed-fail on a core-standard floor row for being durable: the retry lands at t+30 s, the window closed at t+20 s, and the assertion read "a 500 from the subscriber MUST be retried" against a host that retried. The wait now derives from the advertised policy — the 20 s floor is unchanged when nothing is advertised, widening to a 90 s cap for fixed/exponential. The cap is deliberate: an unbounded wait would let a host that never retries hold the suite open instead of failing. This is rc.67's poll-cursor defect one file over and deterministic rather than flaky — the instrument's own window, attributed to the host.

The rule both defects violate, now stated twice in the suite: a scenario must not blame a host for a deadline the scenario chose, and it must read the carrier the spec names rather than the one that was convenient.

Not changed, and deliberately. The attempts.length <= maxAttempts assertion rewards over-advertising, because a host claiming a larger budget than it honours passes while one claiming a smaller budget fails. The honest lower bound is unwitnessable from outside — the suite cannot make a host exhaust its budget without waiting the whole backoff chain — so the asymmetry is recorded here rather than papered over with an assertion that would only appear to close it.

2.0.0

2026-09-05

openwop v2

The v2 major is cut. v2.0.0 is the corpus tag; @openwop/openwop-conformance@2.0.0 and @openwop/spec-artifacts@2.0.0 publish from it as one release under two names.

The cut predicate, machine-true rather than asserted. RFC 0167 §F says "the v2.0 tag is cut when every predicate below is machine-true on the release candidate. None is a header edit." scripts/check-cut-gates.mjs is that sentence in executable form, and all ten pass against a live host with --host-discovery --network: Identity, Registers, Closure, Deprecation, Paths, Codemods, Waiver, Witness, Coexistence, Front door.

What the evidence actually says, including what it does not. The reference host openwop-host-v2-reference certifies all three profiles — openwop-discovery-core (3), openwop-core-standard (13), openwop-conformance-seams-v2 (4) — at 181 / 0 / 0 / 42 / 0 over 223 rows and 1551 assertions. It is also the only host that does: both production hosts are mid-migration, and their own measurements put the remaining distance in capability adverts rather than implementation (myndhyve at 130 / 0 / 88 / 6, claiming openwop-discovery-core alone). No independent-tier host is anchored in the matrix, so leg (b) of the v1 end-of-support clock does not apply; leg (a) governs and v1 support runs to at least 2026-12-04. RFC 0167 §157 recorded the governing question in advance — whether the umbrella's Accepted predicate should additionally require a third-party host at the cut — and answered it no under sole-steward operation.

The through-line of the rc series, since it is the thing a reader should take from the changelog below. Nearly every release closed a defect that let an incomplete or dishonest result read as an honest one: a profile claimed by hosts that never advertised it, a scenario that passed by comparing zero to zero, a guard that reported on its own scope instead of the condition, a changelog that shipped to npm without mentioning the major it shipped in. Five of the last seven were found by host implementers building against the corpus rather than against each other; two were found by checking claims I had made myself. The vocabulary that makes those findings expressible — inapplicable for an obligation never taken on, blocked for one taken on and unmeasurable, and no certification over any blocked row — is the part of v2 most worth carrying forward.

Fixed

  • v2-poll-cursor-v2 was flaky, and its flake blamed the host — on a core-standard floor file, so certification was non-deterministic (suite 2.0.0-rc.67; found by myndhyve, which refused to file it as either a defect or a flake until it had the evidence to say which). The leg compares two reads of the same log — a full read, then a cursor read — but terminalRun() waits only for the run's status to be terminal, and a terminal status does not mean the log has stopped appending. A host emitting trailing or vendor rows adds an event between the reads, and the assertion reports expected [ 1 ] to deeply equal [] against a host whose cursor is correct. Measured: failed twice under a full-suite run, passed 6/6 in isolation on the same revision — a window widening under load, not a wrong cursor. The leg now re-reads the whole log after the cursor read; unchanged means the reads straddled a quiet window and the comparison is decidable, grown means the suite records blocked naming the growth rather than failing. A scenario must not compare two observations of a moving target and attribute the difference to the host.

Fixed

  • The CHANGELOG that ships to npm had not mentioned this major at all — twenty-one releases of it (suite 2.0.0-rc.66). conformance/package.json files lists CHANGELOG.md, which resolves inside the package, so the published file is conformance/CHANGELOG.md — a different file from this one, which is not packed. The shipped file stopped at 1.156.0 (2026-09-02) and carried zero mentions of any 2.0.0-rc, while rc.45 … rc.65 were published on top of it. Anyone installing rc.65 and reading its changelog read a document that ended three days before the v2 series began. Nothing compared either file to the version being published, and the one being edited every release was the one nobody receives. scripts/check-shipped-changelog.mjs (new, wired into openwop-check.sh) now fails whenever a packed CHANGELOG's newest heading is not its own package's version; it caught this on its first run.
  • And a false constraint I stated and shipped, which is how the above was found. The rc.65 entry and commit say the rc.64 entry could not be edited "because CHANGELOG.md ships inside the published conformance package". That is wrong — the root CHANGELOG.md is not packed, so editing a past entry here breaks no identity check. I asserted it to a peer host, which reasonably took it at face value and wrote its correction wording around it. Going only to check whether the claim was true is what surfaced the twenty-one-release gap, which is the larger defect by some distance. The rc.64 and rc.65 entries are still left unedited — but as an editorial choice about not rewriting a published record, which is defensible, rather than a technical constraint, which did not exist.

Fixed

  • Correction to the rc.64 entry. That entry attributes to openwop-app the reasoning that "an escapes-only projection would read empty on both sides", and describes the host as recording escapes only. The host's maintainer has since retracted that: openwop-app does have a Layer-2 invocation log with a claim/release/complete lifecycle (invocation_claim, invocation_log), installed alongside the escape ledger at boot; what it lacks is a list-by-run read, which is a projection gap and not a missing subsystem. The rc.64 scenario fix stands on its own — it corrected a host-independent defect in v2-effect-seam-no-refire and was not predicated on any host's shape. (Wording supplied by the host; the rc.64 entry is not edited because CHANGELOG.md ships inside the published conformance package and rewriting it would break the packed-content identity check for a version already on npm.)

Added

  • v2-run-fork-prefix is ruled OUT of the openwop-core-standard floor, with the general test written down (suite 2.0.0-rc.65; the open question from rc.60). check-declaration rule 10 admits the file mechanically — it gates only on replay, which is in the predicate, and carries no seam token — but rule 10's stated intent is that a floor file be "witnessable by an honest holder of the predicate", and this one is not: it is a single it gated entirely on the conformance-multi-node fixture, and no spec text makes that fixture mandatory, so an honest replay holder without it contributes zero witness. Eleven of the thirteen current members have no fixture gate at all; the two that do (v2-run-cancel, v2-run-pause-resume) gate only some legs of a multi-it file, so the file still witnesses — the rc.59 partial-witness: pattern. The test for any future candidate: can the file still witness for a predicate holder that advertises no optional fixture? If the answer is no because it is single-leg and fixture-gated, it is a valid scenario and not a floor member. This says nothing against the scenario — the boundary it checks is real and a tier-1 host shipped the bug it catches.

Fixed

  • v2-effect-seam-no-refire passed on an empty ledger — 0 <= 0 certified suppression it had never observed (suite 2.0.0-rc.64; found by openwop-app, which refused to ship the projection that would have gone green). The leg fetched the fired run's effect ledger, checked only that the status was 200, forked in replay mode, and asserted countOf(fork) <= countOf(parent). A host whose ledger records nothing for the fired seam — an escapes-only ledger, where a correctly-suppressed effect writes no row at all — reads 0 on both sides and passes. A comparison against an empty source measures nothing. The leg now records blocked when the source ledger is empty: this host advertises the seams profile, mounts the seam, and the seam reported firing, so the obligation is taken on and its declared witness (RFC 0173 §C.2) produced nothing — which is the blocked-versus-inapplicable rule of rc.62/rc.63 applied to a witness rather than an advert.

- The host found it by reasoning about what its own honest implementation would produce, not by running anything: it worked out that an escapes-only projection would read empty on both sides, saw that the assertion would pass anyway, and stopped. That is the second time today a green was caught before it was ever produced.

Added

  • schemas/v2/effect-ledger-projection.schema.json now says what state obliges (suite 2.0.0-rc.64). The four-value enum is the vocabulary a host labels attempts with, not a set of states every host must produce — a host that only claims and completes attempts emits only claimed and completed, and that is schema-valid and witness-sufficient. Verified rather than asserted: no v2 scenario or corpus script reads state; v2-effect-seam-no-refire compares row counts and v2-effect-identity-business-key reads effectId/providerKey across attempts. So released and escaped are projection surface for operators, not witness surface for the suite. The description also names the one thing a host MUST NOT do — record only exceptional attempts — because that is precisely what makes suppression unwitnessable, and it is the shape the reporting host had.

Fixed

  • rc.62's fix was incomplete and its guard did not guard — both corrected (suite 2.0.0-rc.63; the gap found by myndhyve reading its own bundle, the guard defect found by sabotage-proving the correction). rc.62 changed the fifteen scenarios that call seamsProfileAdvertised directly. It missed conformance/src/lib/era2-seed.ts, whose era2Gate mints the same blocked one call deeper for six scenarios that never name the predicate themselves — v2-era-2-append-vocabulary, v2-fork-a-v1-run, v2-pinned-run-disposition, v2-stream-sse-projection, v2-unmapped-type-refused, v2-v1-events-translated. On MyndHyve's bundle that left 16 rows still blocked on advert-absence after the fix — the same defect wearing a different import. era2Gate now records inapplicable; the blocked it returns for unreachable discovery and for a seam that does not answer is untouched, because those are real failed measurements.

- The tell was in rc.62's own evidence and was not chased. v2-fork-a-v1-run is on the seams floor and carried 3 blocked rows on both production hosts, yet the rc.62 sweep found no seamsProfileAdvertised blocked line in it. That discrepancy was observed, noted, and not followed.

  • check-declaration rule 11 was scoped to scenario bodies, so it passed while the rot persisted. A disposition minted in a shared lib/ helper was invisible to it — the guard reporting on the files its author happened to enumerate rather than on the condition, which is the exact failure it exists to catch. It now reads every file under conformance/src/{scenarios,lib} that names the predicate (16 today).
  • And rule 11's window was too narrow to survive its own documentation. It matched the disposition within 3 lines of the guard; adding an explanatory comment between the guard and its return pushed the disposition out of that window and the rule silently passed a deliberate regression. Caught by sabotage-proving the correction rather than by trusting the green. It now scans forward to the branch's first disposition, skipping comment-only lines, and is proved against a regression in both a scenario and the helper.

Fixed

  • The seams profile was claimed by every v2 host and its floor blocked on an advert nobody had made — denying certification of EVERY profile to any host that had simply not mounted the conformance seams (suite 2.0.0-rc.62; found by openwop-app while writing its Phase 4 exit ledger, which measured the profile registry instead of reasoning from the family names). Two independent defects that were harmless apart and fatal together:

- spec/v2/declaration.json gave openwop-conformance-seams-v2 an empty predicate, and claimedProfilesForV2 treats families.every(...)/metadata.every(...) on an empty list as vacuously true — so the profile was claimed by every v2 host. Both production bundles carry it today at certified: false, a claim neither host ever made. The predicate now requires conformance at the discovery root. - Fifteen scenarios — not the three first reported — recorded blocked when seamsProfileAdvertised was false. blocked is bundle-wide fatal (verifyBundleV3 blocked-certified, RFC 0168 §E.1), so an unadvertised instrument denied certification of every claimed profile. Measured: 19 of MyndHyve's 45 blocked rows and 9 of openwop-workflow-engine's 29. All fifteen now record inapplicable with their reason.

  • The corpus already contradicted itself twice on this, which is what settled it. conformance/src/lib/soft-skip.ts documents the vocabulary with these exact examples — softSkip('inapplicable', 'host does not advertise X') versus softSkip('blocked', 'seam ... not mounted') — and "seams profile not advertised" is the first verbatim. And v2-manifest-ceiling-refused got the distinction right nine lines after getting it wrong: line 100 blocked on advert-absent, line 109 blocks on "seams profile advertised but the seam is not mounted". Only the second is a failed measurement; the first is a host that never took the obligation on. The advertised-but-unmounted branches are deliberately unchanged.
  • RFC 0168 §C.1 named an advert location no host publishes and no code reads — "a host advertises the profile in profiles[]" — while seamsProfileAdvertised tests conformance.seamsProfile and the reference host serves no profiles[] at all. Corrected with a dated erratum; the sentence's actual requirement (the advert names the profile rather than setting a testSeams boolean) is unchanged.
  • Why this was not left as documentation. Under the rejected reading — that mounting the seams is a precondition of certifying anything — an independent implementer would have to build conformance test surface before claiming openwop-discovery-core, which is three assertions about a well-known document and has nothing to do with seams. That is a barrier aimed squarely at the independent-tier host leg (b) of the v1 end-of-support clock requires, and all three current hosts are self tier. Seam-gated obligations stay honestly unwitnessed for a non-seams host: a lower witnessCount and an inapplicable row naming the reason.

Added

  • check-declaration rule 11 — an absent seams advert MUST be inapplicable (suite 2.0.0-rc.62). The branch guarded by !seamsProfileAdvertised(...) is read in every scenario that checks it (15 today) and the gate fails if it records blocked; the advertised-but-404 branch is deliberately not matched. Sabotage-proved: flipping one file back to blocked reddens exactly that file. The rule exists because the two facts are one word apart in the source and fatally different in the bundle.

Added

  • The reference host re-cut on suite 2.0.0-rc.61 — 181 / 0 / 0 / 42, all three profiles still certified. evidence/v2-host-bundles/openwop-host-v2-reference.json at build d029f8fe (openwop/openwop-examples#35): 223 rows, 1551 assertions, openwop-discovery-core (3), openwop-core-standard (13) and openwop-conformance-seams-v2 (4) all certified, check-cut-gates.mjs --host-bundle --host-discovery --network PASS on all ten §F predicates. The rc.59 bundle went stale the moment rc.60 landed and the Front door gate said so — "1 of 72 scenario file(s) applicable at major 2 have no row in it, so its totals say nothing about them: v2-run-fork-prefix.test.ts" — which is the gate working: a bundle that never measured a scenario cannot speak for it. The two added rows are that unaided fork-boundary witness (this host is exclusive, so it passes) and rc.61's kind: other note assertion (vacuous here, since this host declares no other seam row). Anchor unchanged — a re-certification replaces the file and does not move it.

Fixed

  • The effect-seam manifest said three different things, and one of them was unimplementable (suite 2.0.0-rc.61; found by a host, openwop-app, building GET /host/effect-seams against the corpus rather than against another host). spec/v2/core/replay.md §The effect-seam manifest described a row as { id, kind: http | provider | webhook | queue | storage | pack | other, suppression, witness?, note? }; RFC 0173 §C.1 described { seam, kind: http | queue | storage | provider-sdk | webhook-fanout, guarded, guardedBy }; schemas/v2/effect-seam-manifest.schema.json requires seam/kind/guarded/guardedBy and is additionalProperties: false. The normative prose therefore named three fields the schema forbids and omitted three it requires — a host that implemented replay.md literally would fail schema validation on every row. Prose is aligned to the schema, which is the artifact the suite validates and both production hosts read.
  • seams[].kind had no description at all, beside a seam documented as "the outbound effect path, host-named" — so a careful reader took kind for the wire protocol, which is what its enum looks like. It now says so normatively: kind names the outbound wire mechanism, not the suite's driving mechanism and not the business purpose, with the many-to-one rule stated (different mechanisms behind one guard are different kinds; one mechanism serving several business purposes is one kind).
  • RFC 0173 §C.1 spelled the path GET /v1/host/effect-seams while core/replay.md, the schema description, facets/replay.schema.json's const and path-manifest.json all spell it unversioned — ten statements against two. Harmless on a dual-stack host, wrong for a v2-only host reading the RFC's normative sentence. Corrected in place with a dated erratum note; the unversioned spelling is normative.

Added

  • kind: smtp and kind: other, and the rule that completeness outranks driveability (suite 2.0.0-rc.61). The accepted enum could not express a direct SMTP connection, so a host with one had to choose between mislabelling a seam and omitting it — and §C.1 requires every reachable path to be listed while replay.md says an omitted seam is invisible to the suite, so both options were dishonest. smtp is added as a real distinct egress; other is the escape for a mechanism no closed enum will ever cover (raw TCP, gRPC, a filesystem write, a device SDK), and a row using it MUST carry note naming that mechanism — enforced by a schema if/then and a new suite assertion, on the same principle as every other honesty rule here: the escape exists and it must state its reason. kind is deliberately not redefined as "what the suite can drive": that would make the vocabulary a property of the instrument rather than of the host, collapse every undriveable seam into one bucket, and end the manifest's auditability against the host's own guard sites. Instead replay.md now states the obligation directly — every reachable outbound path MUST be listed including one the suite cannot drive (its receiver speaks HTTP), such a row is recorded inapplicable naming the mechanism, and a host MUST NOT omit a seam because the suite cannot drive it nor relabel it as a kind the suite can. This is the blocked-vs-inapplicable distinction of rc.42 one level down: a listed-but-undriveable row is a known gap in the evidence; a missing or mislabelled one is a false statement about the host. Additive — enum widening accepts strictly more documents, every existing row stays valid, the if/then binds only rows that opt into other, and the reference host's certified bundle used only pre-existing values, so no re-cut is forced.
  • v2-run-fork-prefix — an unaided major-2 witness that the fork prefix boundary is exclusive (suite 2.0.0-rc.60). replay.md §Endpoint says "events with sequence < fromSeq are fixed history; events >= fromSeq are re-executed", and until now the only major-2 scenario that asserted it was v2-fork-a-v1-run — seam-driven, on the seams floor, so a host that does not advertise the seams profile was never measured on it. The unaided fork scenario, v2-run-fork-refusals, asserts only the refusals and never reads the forked log; the v1 scenarios that do check the prefix are majors: [1]. A tier-1 host shipped sequence <= fromSeq and passed every unaided major-2 scenario, finding the defect itself only when fixing 1-based sequence numbering made the off-by-one visible — two errors cancelling. The new scenario forks a settled conformance-multi-node run at a node.completed strictly inside its log and asserts the inherited rows are exactly [0, fromSeq) and that the source's event at fromSeq does not reappear at that sequence, because it is re-executed. Sabotage-proved: an inclusive prefix (> fromSeq) reddens this leg and only this leg, while v2-run-fork-refusals stays green. It asserts nothing about the re-executed tail — length is not a discriminator, since a node whose start was inherited legitimately restarts, which the reference host demonstrates (source 8 events, fork 9).
  • The reference host re-cut on rc.59 keeps every certification under the stricter floorevidence/v2-host-bundles/openwop-host-v2-reference.json at build 31ec6a12, suite 2.0.0-rc.59: 179 / 0 / 0 / 42 over 221 rows and 1545 assertions, openwop-core-standard witnessCount 8 → 13 and still certified alongside discovery-core (3) and seams-v2 (4), check-cut-gates.mjs --host-bundle --host-discovery --network PASS on all ten predicates. That is the floor addition's own evidence: the five new witnesses cost an honest holder nothing. Anchor unchanged (a re-certification replaces the file and does not move it).
  • Five run-surface witnesses join the openwop-core-standard floor (suite 2.0.0-rc.59; spec/v2/declaration.jsonprofiles.json). v2-run-cancel, v2-run-bulk-cancel, v2-run-pause-resume, v2-run-options-limits, v2-sse-last-event-id — the runs.md §Cancel / §Pause and resume, capabilities.md limits and events.md §SSE frames MUSTs the rc.48–rc.53 retrospectives wrote, each unaided, each with no family gate (rule 10) and no seam token. cancelRun, bulkCancelRuns and pauseRun are core operations by the runs.md surface table, so a host that omits one is blocked and the floor refuses it, which is the prose's outcome; the optional legs (values mode, the delay and cancellable fixtures) record inapplicable with their reason and the file row stays a witnessed pass (partial-witness: marker in detail). Core-standard witnessCount minimum rises from 8 to 13. Measured before the change: the reference host passes all five (rc.58, 179 / 0 / 0 / 42) and stays certified; neither production host satisfies the core-standard predicate today, so no existing claim or anchor moves. Same lane as the rc.45 floor minting: a declaration edit with its note; comment window waived and logged per the v2 charter stance.
  • The reference host certifies every profile it claims and clears every §F cut gate — the first bundle from any host to do so; and a disclosure about the one it replaces. evidence/v2-host-bundles/openwop-host-v2-reference.json is now the rc.58 cut of openwop-host-v2-reference@2.0.0-rc.1 at 763e4e3f (openwop/openwop-examples#32): 179 / 0 / 0 / 42 / 0 over 221 rows and 1545 assertions, openwop-discovery-core (3), openwop-core-standard (8), openwop-conformance-seams-v2 (4) all certified, check-cut-gates.mjs --host-bundle --host-discovery --network PASS on Identity, Registers, Closure, Deprecation, Paths, Codemods, Waiver, Witness, Coexistence and Front door. It replaces the rc.57 bundle checked in earlier today, which — disclosed here and in the INTEROP-MATRIX row — measured a stale local process: the cut script started the host on one port and pointed --certify at another, where a 2026-09-04 build was still listening, while the signature was verified against the started host's discovery. Its 10 executed-fail rows were that process's. The v1 end-of-support anchor for this host stays on that earlier commit by the mechanical rule (the first commit at which the file was non-vacuous, overview.md §v1 end-of-support), fifteen minutes before the honest cut; nothing else MAY set the date, so it is noted rather than moved. The cut script now refuses a bundle whose discovery.url does not name the port it started.
  • The reference host's first non-vacuous major-2 bundle — every v2-table host now anchors, and the v1 end-of-support clock computes its first date on merge. evidence/v2-host-bundles/openwop-host-v2-reference.json: openwop-host-v2-reference@2.0.0-rc.1 at 3f8bff3d, suite 2.0.0-rc.57, signed under the rotated key v2-reference-2 and self-verified, 169 / 10 / 1 / 42 / 0 over 222 rows and 1511 assertions, witnessCount 3 / 8 / 4 on its three claimed profiles (the rc.16 bundle predates witnessCount and was vacuous by the rule). certified: none: the 10 executed-fail rows are the corpus moving past its own reference example — cancel on a terminal run (409 run_terminal), malformed body (400 validation_error), the tenant-bound cross-major read, a fully-implemented pin continuing under the adapter — all rules the runs.md/errors.md retrospectives (rc.40–rc.53) introduced; the host fixes are queued in openwop-examples and a re-certification replaces the file without moving the anchor (overview.md §v1 end-of-support). Checked in first because the anchor needs witnessCount ≥ 1, not certified (runbook §5.2).
  • Second production major-2 host bundle checked in — the tier-1 host, through its front door. evidence/v2-host-bundles/openwop-workflow-engine.json: openwop-app (app.openwop.dev) at f8e24beb7 (serving openwop-app-backend-00678-2sr), suite 2.0.0-rc.56, signed openwop-app-self-2026-09-04 and self-verified, cut at one worker against the deployed revision through Firebase Hosting + Cloud Run, 125 / 2 / 29 / 66 / 0 over 222 rows and 1058 assertions, openwop-discovery-core witnessCount 3 → non-vacuous. check-cut-gates.mjs --host-bundle --host-discovery --network: Identity and Witness PASS; Coexistence FAIL on the seams-gated rows; Front door FAIL on the two v2-poll-cursor-v2 rows (Gap C, host-side) — certified: none, recorded honestly in the INTEROP-MATRIX v2 table. Anchors on merge; the date lands in the follow-up regeneration (runbook §5.3).
  • First production major-2 host bundle checked in — the v1 end-of-support clock has something to anchor on. evidence/v2-host-bundles/myndhyve.json: MyndHyve workflow-runtime at 847d2425e (serving workflow-runtime-00330-cjg), suite 2.0.0-rc.56, signed myndhyve-bundle-2026-09 and self-verified, 128 / 0 / 45 / 50 / 0 over 223 rows and 1407 assertions, openwop-discovery-core witnessCount 3 → non-vacuous per spec/v2/core/overview.md §v1 end-of-support. check-cut-gates.mjs --host-bundle --host-discovery --network: Identity, Witness and Front door PASS (signature attributes to the key published in the host's discovery signingKeys[]; tarball digest matches); Coexistence FAIL on the seams-gated rows, so certified: none — the honest state, recorded in the INTEROP-MATRIX v2 table. The anchor date itself is the merge commit and lands in the follow-up regeneration of evidence/v1-end-of-support.json (runbook §5.3).

Fixed

  • v2-era-2-append-vocabulary could never witness the writer rule — it read events off the response object instead of its JSON body (suite 2.0.0-rc.58). eventsOf(before) / eventsOf(after) were handed the OpenWOPResponse (every other caller passes .json), so seedCount was always 0 and the leg recorded blocked — seedEra2Log reported success but the log reads back empty (0 events) on every host, including the reference host whose poll demonstrably returned both seeded rows. Found while bringing the reference host up to the rc.40–rc.53 prose: after its fixes this was the one row left, and the host's own read said otherwise. Both calls now pass .json; the seams-gated blocked rows on the production hosts are unaffected (their seam is not mounted, which is the earlier, honest reason).
  • v2-coherence-not-in-bundle recorded blocked in the published layout — a corpus-subject row that denied certification to every npm-installed host (suite 2.0.0-rc.57). The scenario reads evidence/corpus-ledger.json and src/coherence/, neither of which the tarball ships, and asserts nothing about a host; RFC 0148 §A defines blocked over advertised behaviour a missing dependency prevented exercising, and there is none here — the same reasoning lib/spec-coherence.ts applies to the v1 corpus scenarios. Its early returns now record inapplicable with the corpus-subject reason. The reference host carried this row as one of its "16 blocked" since rc.16; MyndHyve's first bundle carried it too. Also: conformance/README.md now shows the install as both packages at one explicit version — npm i --legacy-peer-deps on the suite alone does not pull the exact-pinned peer (measured as corpus stamp MISMATCH — missing @openwop/spec-artifacts).
  • The v1 end-of-support anchor was read from whatever history was checked out, so the first non-vacuous bundle could never pass --check on its own PR. scripts/generate-v1-eos-clock.mjs ran git log -- <bundle> on the current ref: on the row PR's branch that resolved to the branch's add-commit, and in CI (a shallow checkout of the synthetic refs/pull/N/merge commit) to that commit's timestamp — a date nobody committed — so the coherence check was unsatisfiable, and on main a shallow clone would have moved the anchor with every commit. Every earlier bundle was vacuous, so the branch had never fired. Now the anchor is what the prose says: the committer date of the first commit on main's first-parent history at which the file is non-vacuous (origin/main, else main; the script refuses without one), so a PR branch reads "not anchored" until merged — the pre-merge state the row PR commits; --check tolerates exactly one difference, a host whose committed record is "not anchored" while the history anchors it at HEAD itself (the merge that just landed it), and names the follow-up regeneration (runbook §5.3); the spec-corpus CI job checks out with fetch-depth: 0. OPENWOP_EOS_MAIN_REF overrides the ref for a merge simulation in a scratch worktree.
  • A leg that said why it returned early was still recorded blocked at it granularity — and a bundle with any blocked row does not certify (suite 2.0.0-rc.56). softSkip() notes were per-file, and the per-it row in setup.ts consulted only the journal's behaviorGate entries, never the note the leg had just written; so return softSkip('inapplicable', 'a2a facet not advertised') produced a per-it row of blocked / unclassified return while the file row (which does read the notes) was honestly inapplicable. verifyBundleV3 refuses certifiedProfiles for any bundle with blocked > 0 (RFC 0168 §E.1), so on 2026-09-05 forty-five such rows on a host that does not advertise A2A/MCP denied certification to every profile it claimed. Now every note carries a sequence number, setup.ts marks the sequence at test start (softSkipMark) and reads the notes written during the test (softSkipDispositionSince), and the per-it rule is the pure resolveItRecord — same fold as the file rule (worst-first, reasons joined), a note-less zero-assertion pass still the unclassified return. Pinned by src/lib/it-record-softskip.test.ts. No file-level or floor row changes; only per-it rows that were wrong become right.
  • --certify --target-major 2 could not write a bundle for any host whose floor files pass (suite 2.0.0-rc.55). The flag set OPENWOP_TARGET_MAJOR on the vitest child only; the runner's own process never saw it. The worker recorded every v2 floor file under openwop.floor.<stem> with its assertion count, the runner mapped the same file to openwop.scenario.<stem> (requirementIdForFile gates on targetMajor() = process.env), found no ledger row, emitted a report-derived executed-pass with no count, and verifyBundleV3 rejected its own bundle as a vacuous-pass emitter defect — exit 2, nothing written. A tier-1 host's rc.54 origin bundle died this way on 2026-09-05 with six "vacuous" floor files: exactly the floor files vitest had marked passed. Now resolveTargetMajor sets the runner's own env, and floorScenarioFiles() also honours the installed v2 floor map (v2FloorsActive()), so the two halves cannot disagree again; src/lib/floor-id-agreement.test.ts runs with the env unset — the runner's state — and pins both. Same defect class as rc.41 (two sources of "is this file a floor?", one blind). Workaround on rc.54: export OPENWOP_TARGET_MAJOR=2 for the runner process.
  • pause-resume.test.ts leg 1 could not pass on any conforming host (suite 2.0.0-rc.54). rc.53 created a 30 s node, paused it immediate, resumed, and polled for a terminal inside the suite's 30 s test timeout. The resumed node.started is a fresh attempt (rc.52), so the node sleeps its full 30 s after resume and completes at about 30.2 s — a timeout by construction, measured by a tier-1 host on the first run of the lane (running +52 ms, paused +60 ms, resume +64 ms, completed +30 233 ms). The node is now 3 s. No host ran this file before tonight, which is how the defect survived rc.53's gate.
  • pause-resume.test.ts (major 1) contradicted spec/v1/rest-endpoints.md in three places and the fixture contract in a fourth (suite 2.0.0-rc.53). The first v1 host to un-skip the file found it: (1) it expected a second :pause on a paused run to be 200/202, citing an "additive contract" the prose does not contain; the prose says 409 with the existing pause's pausedAt in details unless the request carries the original Idempotency-Key, in which case 202 with the cached response — both forms are now witnessed. (2) It asserted error === 'conflict' on the 409s; v1's registry is open and names no code for already-paused or not-paused (only run_terminal for a terminal run), so the scenario asserts 409 and details.runStatus and no code. (3) It paused a 30 s node under drain-current-node and polled 10 s for paused; drain lets the executing node reach a terminal first, so the timing-sensitive legs now use immediate, and a new leg with a 3 s node witnesses the drain semantic itself: node.completed precedes run.paused. A host that yields mid-node under drain is the non-conforming one; "cooperative drain" is rejected. (4) It sent inputs.delaySeconds; the fixtures define delayMs. v1 is frozen and untouched; every fix is the scenario's. The reworded idempotent leg's id carries an alias.

Changed

  • Pause semantics made explicit; drainPolicy has one vocabulary (suite 2.0.0-rc.52; comment window waived as sole steward, logged here). A tier-1 host implementing runs.md §Pause and resume recorded node.failed for the attempt an immediate pause cut, and a replay fork of the paused-and-resumed run then folded that failure and dead-lettered. The prose now says what immediate already implied: the snapshot is taken between events, the interrupted attempt has no terminal node event, a host MUST NOT record node.failed (or any terminal node event) for it, and the resumed run's node.started is a fresh attempt. The record of the interruption is run.paused, whose payload MAY carry interruptedNodeId and interruptedAttempt (additive, kind-bound). And the run.paused payload's drainPolicy enum, seeded at v2 as drain | interrupt with a provenance note pointing at v1 text that never said it (v1's event carries no drainPolicy), becomes the request vocabulary immediate | drain-current-node; the payload echoes the request's word. v2-run-pause-resume's positive leg asserts both: no terminal node event between run.paused and run.resumed, and the echoed word.

Added

  • The events channel has witnesses at major 2 — second retrospective unit (events.md §The events channel and §SSE frames were cited only for frames and payloads). v2-stream-mode-refusal: a streamMode outside the pattern, a forbidden combination (updates,values) and an unimplemented mode are each 400 unsupported_stream_mode with details.supported listing the individual modes; the bogus mode with Accept: application/json is still 400, never 406, because validation runs before content negotiation; updates streams 200 (the control); bufferMs yields at least one event: batch frame with an array data: and the flattened stream equals the unbuffered log ("every frame is batch" is deliberately not asserted — a consumer MUST tolerate an unbatched frame). v2-sse-last-event-id: a resume begins at the next sequence, never re-emits the resumption point, and equals the full set minus every id at or below it; a resume at the last id yields nothing and closes; in values mode the first frame is state.snapshot. Ids are 0-based and tested for presence, never truthiness. lib/sse.ts subscribe() gains extraHeaders (it sent no OpenWOP-Version, so a major-2 caller was served the preferred major). Two prose/registry seams closed in the same PR: unsupported_stream_mode gains its details.supported schema (both v1 and v2 prose required it; the row said null); events.md §SSE frames now says state.snapshot and batch are frame names, not event types (neither is in the closed type enum, correctly). 515 scenario files (v1 445, v2 71). Suite 2.0.0-rc.51.

Fixed

  • Two rc.48 readers looked for details under error; the v2 envelope carries it at the root ({ error: <code>, message, details? }, schemas/v2/error-envelope.schema.json). v2-run-bulk-cancel's over-cap leg reported a tier-1 host as missing details.maxRunIds when the host answered details: { maxRunIds: 100 } correctly — retracted on the bus; v2-run-pause-resume's details.runStatus reader had the same shape. A bulk entry's error is the whole envelope (api/v2/openapi.yaml $ref), so the entry code is read the same way as a top-level one, and the entry is now validated against the envelope schema. Suite 2.0.0-rc.50; a bulk-cancel red on rc.48 or rc.49 is the reader's.

Changed

  • The five runs.md ambiguities rc.48 filed are resolved in the prose and the registry (suite 2.0.0-rc.49; comment window waived as sole steward, logged here). (1) A cancel on a terminal run MUST be 409 run_terminal; a 200 echoing the terminal state is outside §Cancel's 200 grammar. (2) run_state_conflict (409, details.runStatus) joins the registry (97 codes; envelope regenerated): pause/resume refused on a non-terminal run carry it, on a terminal run run_terminal. (3) Inside a bulk-cancel entry identity.md §5 applies exactly as on a path: a foreign tenant segment is id_tenant_mismatch (or not_found), run_forbidden is for a same-tenant run the caller may not cancel, run_terminal for a terminal one. (4) Annotations on a host without feedback answer 404 not_found, as getEvalSummary and getRunAncestry do; the table's 501 had no registered code. (5) The If-None-Match header note is widened from the discovery document to every resource that carries an ETag, the run snapshot included, matching §Snapshot's MUST. run-options.schema.json's tags description now says what the prose says (MUST, not SHOULD; the constraints were already equal). The four scenarios tighten to the resolved answers: v2-run-cancel requires 409 run_terminal; v2-run-pause-resume requires run_terminal on a terminal run and run_state_conflict + details.runStatus: paused on the second pause; v2-run-bulk-cancel requires id_tenant_mismatch / not_found in the foreign entry; v2-run-annotation-not-event records a 404/501 with feedback advertised as blocked (not mounted), no longer inapplicable.

Added

  • The run surface has witnesses at major 2 — eight scenarios for runs.md's written-and-unasserted set (retrospective of Phases 0–3: runs.md carried 61 MUSTs and 12 witness sites, every one under §Create; five times any other core document). v2-run-cancel (terminal-run cancel; positive cancel on conformance-cancellable with run.cancelled within 5 s), v2-run-bulk-cancel (101 ids → 400 validation_error + details.maxRunIds; [own, foreign, own] → 200 in request order, the foreign entry ok: false in its entry and never a top-level 403), v2-run-pause-resume (409s on a terminal run; on conformance-delay: 202 paused / 409 / 202 running with run.paused then run.resumed in the log; the run is cancelled after), v2-run-options-limits (101 tags, a 257-character tag and an unknown create root key → 400; odd-format tags and free-form metadata → 201 and echoed unchanged), v2-run-snapshot-etag (a matching If-None-Match → 304 empty with the version header; a non-matching one → 200 — the control), v2-run-diff-identical (two noop logs → divergedAtSeq: null + [], validated and echoing both ids; a noop-vs-conformance-failure diff MUST diverge — the control), v2-run-fork-refusals (replay + overlay → 400; bad fromSeq422 fork_point_invalid; a valid replay fork → 201 with the parent's owner verbatim), v2-run-annotation-not-event (created, listed, and absent from the log). Every leg has a control in its file; none is on a profile floor. 513 scenario files (v1 445, v2 69). Suite 2.0.0-rc.48.

Five prose ambiguities found and filed, not resolved here: (1) runs.md §Cancel is silent on cancelling a terminal run — 409 run_terminal (registered) or 200-idempotent; the scenario accepts either. (2) §Pause and resume says 409 for already-paused / terminal / not-paused but names no code and only run_terminal is registered; recommended: run_terminal for terminal, one new run_state_conflict (409, details.runStatus) for the rest. (3) §Cancel names run_forbidden for a bulk entry the caller cannot see while identity.md §5 names id_tenant_mismatch / not_found for a foreign tenant segment; recommended: identity.md wins for a foreign segment, run_forbidden for a same-tenant run the caller cannot see. (4) §Surface says annotations answer 501 when feedback is unadvertised; the registry has no 501 code but credential_unavailable; recommended: 404 like eval-summary and ancestry in the same table. (5) headers.md scopes If-None-Match to the discovery document with SHOULD 304 while runs.md §Snapshot applies it to the run snapshot with MUST 304; the declaration's header note is narrower than the prose. Also: run-options.schema.json tags description says SHOULD where the prose says MUST (wording only; the constraints match).

Changed

  • docs/runbooks/V2-HOST-MIGRATION.md — "Phase 5 — Anchor". What a host does to anchor the v1 end-of-support clock: the row PR checks in the signed bundle under evidence/v2-host-bundles/<host.name>.json; non-vacuous means witnessCount ≥ 1 (re-cut on rc.45 or later, because earlier emitters printed 0 regardless); the anchor is the squash-merge date, so it lands in a follow-up regeneration commit, with the short visible red on main between them explained; and the three clock sentences a reader should expect to see printed.

Added

  • Old-major retention floors have a normative home and a gate. The charter's Phase 5 line ("npm, PyPI and Go old-major retention floors honored, 12 months from the 2.0.0 publish") appeared nowhere in the corpus. overview.md §v1 end-of-support now states it: @openwop/openwop 1.x, @openwop/openwop-conformance 1.x, openwop-client 1.x and the Go module v1.x MUST remain installable at their last 1.x version for 12 months from the v2.0.0 tag, independent of v1 end-of-support. spec/v2/retention-floors.json pins the identities and last versions (hand-kept, measured live 2026-09-05: 1.9.0, 1.163.0, 1.7.0, v1.6.0); scripts/check-retention-floors.mjs reads the floor start from git (null until the cut), prints the state every run, and with --network probes each registry for the pinned version, failing a missing one while the floor is open. Coherence scenario v2-retention-floors drives it both ways with a canned probe and synthetic dates; its stem joins the bundle schema's coherence lookahead (packed content), so the suite is 2.0.0-rc.47.
  • Phase 5 entry: the v1 end-of-support clock is computed, not asserted. overview.md §v1 end-of-support names the host set (rows in the INTEROP-MATRIX v2 table), what "non-vacuous" means (a claimed profile with witnessCount ≥ 1), and the anchor: the commit at which a host's signed bundle, checked in under evidence/v2-host-bundles/, first became non-vacuous — read from the public history, never from generatedAt, which nothing signs; a re-certification replaces the file and does not move it. scripts/generate-v1-eos-clock.mjs computes both legs (every host's anchor + 90 days; 18 months from v2.0.0 iff an independent host was anchored at release) into evidence/v1-end-of-support.json and always prints the clock state, because "not anchored" and "far away" would otherwise print the same nothing. check-removal-dates.mjs reads it: on or after the date, a v1-end-of-support row's v1-tree sources fail. Proven both directions with a synthetic clock. Today the clock is not anchored: the one v2-table host's public bundle (rc.16) is vacuous on every profile — witnessCount: 0 — which is the rc.45 defect measured on a real bundle, and it also carries the openwop.floor.any.interrupt- blocked row. The anchoring commit is the squash-merge that lands a non-vacuous bundle, whose date the re-certification PR cannot know, so the anchor lands in a follow-up commit that regenerates the clock file; --check names that when it fails. The corpus-coherence scenario v2-eos-clock drives the script both ways; its id joins the bundle schema's coherence lookahead (packed content), so the suite is 2.0.0-rc.46.
  • docs/migration/v1-to-v2.md cites both hosts' PR series as they stand (merged by number, open marked open); status moves from "incomplete by design" to "in flight", complete only when both origin bundles are in the v2 table.

Fixed

  • A v2 profile certified on no evidence. Two of the three v2 profiles declared an empty floor (openwop-core-standard, openwop-conformance-seams-v2); at major 2 the verdict for an empty floor was certifiable: false, the emitter's certified flag never read the verdict (!notHeld && !rejected && blocked === 0), and witnessCount read the v1 hand table and printed 0 for every v2 profile. Three more floor sites were unjoined besides: discoveryOnly / runtimeDerived came from the v1 table at major 2, so openwop-discovery-core was certifiable whatever its v2 floor file said; and the v1 table's prefix group (interrupt-) was still derived at major 2, where no interrupt- file runs, so every major-2 bundle carried one openwop.floor.any.interrupt- row recorded blocked — which by RFC 0168 §E.1 denied certification to every profile on every major-2 bundle ever cut, including the rc.1 reference row's. Found by the unit test below, not by a host: the row read as one of the "unclassified returns" in the blocked count. Now, at major 2: the floor is the declaration's; certified is the verdict; a profile certifies only with at least one witnessed pass on its floor (inapplicable is honest per file and cannot be the whole floor; skipped — an opt-in the host withheld — blocks); witnessCount is that number. The three floors are minted in spec/v2/declaration.json (rc.45), each file read for its gate: discovery-core (3 unaided), core-standard (8, one or more per predicate family), seams-v2 (4, every seam-driven witness). v2-effect-seam-manifest's seam-driven no-re-fire leg moves to its own file, v2-effect-seam-no-refire, so the manifest witness can sit on the core-standard floor without a seam. check-declaration rule 10: a profile with a predicate MUST declare a floor; a floor file's family gates MUST be inside the predicate; a seam-driven file sits on the seams floor only. Notice to hosts: a floor file MUST NOT be opted out (OPENWOP_OPTED_OUT_SCENARIOS) — with a ledger present a floor id with no row is an unclassified return and --certify exits 3; let it run and soft-skip. The cut gate (check-cut-gates.mjs) reads none of floors, certified or witnessCount, so no bundle that passes it today fails it after this. 505 scenario files (v1 445, v2 61). Suite 2.0.0-rc.45.

Changed

  • docs/runbooks/V2-HOST-MIGRATION.md — sixth scoped-signal instance: a hosting layer is part of the wire. A direct-service-URL witness cannot see a front door that decodes %2F or a backend that links to its own hostname over http; ids and absolute URLs must be witnessed through the origin a client is given (§4.2d). Credited to the host that found it in its own ADR correction.

Added

  • v2-created-run-readable — a run created at a base MUST be readable and pollable at that base by the id the 201 returned, and eventsUrl / statusUrl MUST resolve under the request's origin without a scheme downgrade (runs.md §Create now says both; a relative path satisfies the link rule). Until now the read-back after a create was soft-skipped as blocked on any non-200 (v2-id-grammar) — the honest word when a host will not let the suite look, the wrong word when the host answers 404 No route matches for the id it minted seconds earlier. Measured 2026-09-05 on a tier-1 host: its public origin decodes %2F to / before forwarding, so every tenant-bound id is unreachable through the front door while the direct service URL answers 200; the same create response links eventsUrl to http://<direct service host>/…. Proven before commit: the scenario fails the origin on the read-back (404) and fails the direct service URL on the link (scheme downgrade httpshttp) — both real, neither blocked. 504 scenario files (v1 445, v2 60). Suite 2.0.0-rc.44.
  • payload_too_large (413) and unsupported_media_type (415) join the error registry (spec/v2/errors.json, 96 codes; the envelope schema is regenerated). Both production hosts had to answer a body-parser refusal on the major-2 wire and the closed registry offered nothing: one emitted a host-prefixed openwop-app.payload_too_large (correct under the registry rule), the other a bare payload_too_large (a code claiming to be protocol). A size or media-type refusal on a core endpoint is as universal as validation_error; a client should not switch on N vendor spellings of one condition.

Fixed

  • An unreadable discovery document at suite init no longer reads as "the host advertises no fixtures". setup.ts fetched /.well-known/openwop once, with a 5 s abort, without the lane's OpenWOP-Version, and on any failure set the fixture cache to empty — so all 311 fixture-gated sites recorded inapplicable, indistinguishable from a host that advertises nothing. Measured 2026-09-05: all four workers aborted at 5 s against a host answering in 200 ms (a Cloud Run cold start). Now 20 s, two attempts, the lane's major's representation, and on failure the cache stays unknown: every fixture gate records blocked with the init reason. Proven both directions (404 base → blocked; readable → unchanged).

Fixed

  • **A major-2 floor was minted openwop.scenario. and looked up at certify time as openwop.floor..** The ledger decided "is this file a floor?" from PROFILE_FLOOR_SCENARIOS — the v1 hand table, which knows no v2 file — while --certify derived the v2 floors from spec/v2/profiles.json privately. On a tier-1 host's first production bundle: openwop.scenario.v2-capabilities-root-closed executed-pass with 5 assertions, both claimed profiles witnessCount: 0 over 101 executed-pass rows, REJECTING — openwop-discovery-core: unclassified. One derivation now (v2ProfileFloorFiles, requirement-registry.ts), read by both the ledger at target major 2 and the CLI; proven with a fresh build — the file mints openwop.floor.v2-capabilities-root-closed at major 2 and openwop.scenario.… at major 1. check-declaration rule 9: a declared floor MUST name a scenario file that exists.

Added

  • v2-malformed-body-envelopePOST /runs and POST /webhooks with body { under major 2 MUST be answered by the host: 400, the JSON error envelope with validation_error, and the OpenWOP-Version header (§1.4, on every response). A tier-2 host found its Express JSON parser mounted before negotiation, so a malformed body escaped to a framework HTML 400 with no header under both majors — the one request no scenario sends by accident. The host suggested the probe; it creates nothing and also distinguishes a host from a hosting fallback in front of it. Measured before commit: it fails a tier-1 host's public origin (SPA fallback, 200 text/html) AND its direct service URL (500 internal_error, no header — the same parser-before-negotiation gap, on the other host). 503 scenario files (v1 445, v2 59).

Fixed

  • v2-advertised-path-space-served accepted a hosting fallback's 200 text/html as "mounted under major 2". A tier-1 host's public origin rewrites /.well-known/ and /v1/ to its backend and lets every unversioned major-2 path fall through to the SPA shell — 200, no OpenWOP-Version header — while the Cloud Run URL one hop behind answers every path correctly. The scenario was green on production for ten hours because a shell and a mount share a status code; a second-party witness against the public origin then failed 17 scenarios on that fallback. "Reached under major 2" now requires the OpenWOP-Version response header (versioning.md §1.4, on every response) and a non-HTML body. Proven both directions: fails against the public origin, passes against the direct service URL.

Changed (compatibility note, retroactive)

  • conformance/src/lib/saml-idp.ts changed the bytes it signs in #1163 (f36ca6d0, 2026-09-01) and shipped without a note. The RFC 0163 §B two-trust-root fixture moved <saml:Issuer> inside the signed element and changed the fixture's ad-hoc canonical string. src/ ships in the package, hosts import createSyntheticSamlIdp from it, and a host that had mirrored the fixture's canonicalization instead of doing C14N began answering bad-signature for every minted assertion — valid, expired and not-yet-valid alike — with no failure anywhere but its own test suite. The change's own comment claimed "a lone instance behaves exactly as before"; that was true of verify() and false of any verifier that reconstructed the old form. A tier-2 host traced it on 2026-09-05. The remedy is the §B form (verify the Issuer inside the signed element as the trust root), not a pin; but a fixture whose signed bytes are a de facto contract for hosts is packed content, and its signed form MUST NOT change again without a line here.

Added

  • v2-stream-sse-projection — every data: frame on the major-2 run stream MUST carry the run's tenant-bound runId, asserted per frame. Both production hosts shipped the same defect on the stream path (one had no per-frame projector; the other projected per frame and not in the batch flush) and the suite could not see it: of 56 v2-* files, none read a stream frame. Found by a live witness and a peer's report; a tier-1 host asked for the file by name. 502 scenario files (v1 445, v2 58).

Fixed

  • A scenario now refuses to probe under a contract its gate did not use. scenario-majors.json says which target majors a file is written for; the driver reads OPENWOP_TARGET_MAJOR to pick every probe's header and path space; nothing connected the two. A lane that ran vitest over all 501 files at the default (major 1) executed every major-2 scenario with major-1 requests — the scenarios' gates call v2Discovery(), which sets the header explicitly, so the gate passed and the probe went out as v1. Measured on a tier-1 host: three phantom host defects from one such lane, and four v2 files red on every host forever under a major-1 driver. src/setup.ts now records inapplicable (with the reason, through the same journal entry behaviorGate writes) and skips any file whose registered majors do not include the lane's major. All 501 files, one place. Proven in four directions: v2 file at lane 1 → inapplicable; same file at lane 2 → runs; v1-only file at lane 2 → inapplicable; both-majors file → runs.

Added

  • eval-summary.pinnedNodeIds[] — the node ids whose outputs were pinned for an eval run, ids only. A tier-1 host proposed pinned: true on each node.completed and asked before building; replay-ness is a run property (replay.md §Determinism 5, webhooks.md §Replay), so the per-event flag was refused, and because the summary MUST be content-free of task output the seat carries scope, not values. The values stay on the eval run's own snapshot behind the run's auth.

Added

  • run.completed MUST carry outputs, and v2-run-completed-outputs witnesses it. Both majors' payload schemas named the property and required nothing; v1 also left the object open. A tier-1 host emitted the singular output for its whole life and validated every time — v2 closing the object caught the extra key, and nothing in either major has ever caught an absent one. Seventeen scenario files mention run.completed; none read its payload. runCompleted.required is now ['outputs'] (an empty object is a valid value), events.md §Payloads says so, and the witness asserts the effect on the terminal event of a run that actually completed.

Fixed

  • webhook-delivery.schema.json required workspaceId; identity.md makes owner.workspace optional. The envelope was tighter than the identity model it renders: a run in a single-workspace tenant has no workspace, so the only conforming emission was to put the tenant id in the workspace field — which is precisely what a tier-1 host proposed (owner.workspace ?? tenantId) and asked to have vetoed. Vetoed. workspaceId is now optional and present exactly when owner.workspace is; §Delivery says so and forbids the substitution. Six hours old, same defect class as the check-tighter-than-its-prose it was written beside.

Fixed

  • removalTrigger could not express what RFC 0176 §C.2 requires. The wrapper, the dotted mirror, Capabilities-Etag and the /.well-known/wop alias have two independent removal events — absent from the v2 representation at the cut, and removed from the v1 representation at end-of-support — and the field was a scalar. The four rows carried v1-end-of-support alone while v2-well-known-one-resource asserted the v2.0-cut obligation by name: the scenario was right about the effect and the data could not say so. The field now accepts a set (a bare string stays valid); the four rows carry both. Phase 5 planning had proposed deleting the "unused" v2.0-cut value — it was not unused, it was inexpressible.

Fixed

  • The id-kind binding check could not see plural arrays, and the one inbound v2 surface that takes them was unbound. check-id-kinds-bound.mjs matched /Id$/; runIds, sourceRunIds, contributingRunIds, nodeIds are Ids. POST /runs:bulk-cancel inherited v1's items: {type: string} through the derived OpenAPI, so a v2 client sent the tenant-bound ids it had been handed and every one answered not_found, per id, silently — a tier-1 host found it while auditing its own inbound paths. The projection had covered the way out and not the way in. The check now yields each Ids property's items schema; four arrays bind to their kinds, four are triaged not-a-kind (nextWorkerIds is a documented union of node-ids and agent-ids, so no single kind can hold it), and the bulk-cancel body is bound at the generator. Sabotage-tested.

Added

  • docs/migration/v1-to-v2.md — the v1→v2 migration guide, written from the two migrations that actually happened. A Phase 5 deliverable landing early: neither host has finished, so it does not yet cite both PR series (it says so at the top). What it does carry is what the migrations have already cost — the projection that reached only "JSON senders" and silently unmatched every webhook, the --target-major default that ran v2 scenarios over v1 requests and produced three phantom host defects, the bundle that passed with every row blocked, and the nine suite versions (rc.20–rc.28) that no consumer can install. Organised around the one shape all of them share: a signal that reads the same whether it is true or false.

Added

  • identity.md §5 is now enforced — scripts/check-id-kinds-bound.mjs + spec/v2/id-field-bindings.json + a coherence scenario. The rule ("every id field in every v2 schema and every api/v2/openapi.yaml parameter and response body MUST $ref its kind") was published and never checked. 34 violations, not the 7 a name-matching check would find: only 20 of 88 Id properties share a name with a kind, and childRunId sat as {type: string, minLength: 1} in the same file where parentRunId was correctly bound — so a child run's identifier carried no tenant segment for the mandatory 403 id_tenant_mismatch refusal to read. nodeStarted.typeId had no pattern at all. Coverage is now explicit: every Id property is either bound to a kind or declared not-a-kind with a reason, and an untriaged field fails.

Fixed

  • The typeId kind rejected ids that a legal pack name generates. node-pack-manifest.schema.json's name admits _; the kind did not, and a pack's node type ids are derived from its name — so vendor.acme.my_tools could not declare vendor.acme.my_tools.echo. The kind now admits _. A scan of 539 distinct typeId values found zero affected either way: the conflict was between the two grammars, not in the population, which is why counting values did not reveal it.
  • typeId was the only kind with no length bound. Every other $def bounds length inside its pattern ({1,128}, {16,128}); typeId's quantifiers were open-ended, so binding the node-pack properties to it would have dropped their local maxLength: 256 rather than relocating it — a loosening disguised as a tightening. The bound now lives on the kind.

Added

  • schemas/v2/webhook-delivery.schema.json — the delivery envelope had no schema. webhooks.md §Delivery specified the body as { runId, workspaceId, event } in one sentence and nothing bound it, while the NESTED event.runId was bound all along via run-event.schema.json. A host that projected runId on responses but not on outbound emissions handed subscribers an id the client never saw — correlation matching nothing, with no error, no 4xx and no log line. Two production hosts found the asymmetry independently and both concluded it was a missing artifact rather than a missing paragraph. §Delivery now cites the schema and states the tenant-bound rule for emissions.

Fixed

  • v2 overlap: a run minted under major 1 MUST be named by its tenant-bound projection when read under major 2. spec/v2/core/versioning.md §5 described the dual-stack scenario's shape and said nothing about the identifier, so v2-dual-stack-negotiation asserted byte-equality with the v1 id while identity.md §5 mandated <tenantId>/<opaque> — a host implementing both faithfully could not satisfy either check. §5 now states the rule, and states why it is a MUST: a bare id carries no tenant segment for the mandatory 403 id_tenant_mismatch refusal to read, so a legacy unprefixed form would exempt exactly the v1-carried ids from major 2's tenant isolation. ids.schema.json gets no legacy branch.
  • api/v2/openapi.yaml components/parameters/RunId could not express a conforming runId. Typed inline as maxLength: 128; the tenant-bound grammar reaches 257 characters. Now $refs ids.schema.json#/$defs/runId, which identity.md §5 already required and nothing checked.
  • v2 charter Phase 4 — an assertion tighter than the prose it cites, which is the mirror of a schema looser than the prose it enforces. v2-dual-stack-negotiation asserted the v2 cross-major read returns a runId byte-identical to the one v1 minted, citing versioning.md §5. §5 says no such thing — it describes the scenario's shape ("creates one run through /v1/runs … reads it through /runs with OpenWOP-Version: 2; the response headers name the contract used") and is silent on the identifier. identity.md §5 is the section that speaks: under major 2 a runId is tenant-bound <tenantId>/<opaque>, a grammar in ids.schema.json. A host implementing both sections faithfully cannot satisfy byte-equality — the tier-1 host's identity branch turned red on exactly this, and it reported the red as its own regression rather than filing a corpus bug to excuse it. The assertion now requires what §5 actually requires: the read names the same run — the v1 id verbatim, or its tenant-bound projection — and rejects anything else, which would be a different run. Verified still green against the deployed host, which does not project yet. The general form: a check tighter than its text and a schema looser than its text are the same defect from opposite sides, both are gaps between a check and the prose it cites, and both stay invisible until a host implements the prose rather than the check.
  • v2 charter Phase 4 — a v1 contract that was unimplementable as written, and two "hazards" beside it that are correct and MUST NOT be fixed. Both hosts, chasing the stamping MUSTs, converged on three findings in run-snapshot.schema.json. One is a real defect: engineVersion was typed "string" while version-negotiation.md §Stamping says "Every persisted run document MUST carry an engineVersion: number field" — so a host obeying the prose emitted a document that failed its own schema, and a host obeying the schema violated the MUST. spec/v1/extensions.json already called the axis "a number, not a feature" and schemas/v2 types it integer (RFC 0172 §B axis 3): prose, register and v2 agreed, and only this file dissented. Corrected to number, and safe because both production hosts were measured emitting the field not at all, so no existing document is invalidated. The other two are NOT defects and the corpus now says why, because both hosts proposed to "fix" them: (1) eventLogSchemaVersion: {minimum: 0} — flagged as "a schema-valid run document can be born legacy", but §Legacy detection defines a legacy run as one whose value is "undefined or < 2", so a legacy document carrying 0 or 1 is contemplated by the contract and MUST still validate; raising it to 2 in v1 would make the legacy rule unrepresentable. v2 uses minimum: 2 correctly, because the v2 era key admits only 2 and 3. (2) Neither field is in required — but §Stamping grants engineVersion an explicit "MAY omit … on legacy runs" escape and legacy runs have the era key undefined by definition, so neither can be required without contradicting the prose that requires them. Also recorded, from openwop-app and confirmed by MyndHyve: the damage from a missing required field is set by the reader rule for absence, not by the strength of the requirement — absent engineVersion reads "compatible" and is benign; absent eventLogSchemaVersion reads legacy and makes a conforming client ignore the event log the host is serving. Same file, same MUST strength, opposite cost.
  • v2 charter Phase 4 — the docstring I wrote to stop a docstring lying went stale in one release candidate. Two candidates ago I corrected version-negotiation.test.ts's comment, which had claimed coverage of four version axes while asserting one, and wrote that engineVersion "remains UNASSERTED". One candidate later I asserted it — and the comment I had just written to be honest became false in exactly the way the original was. Corrected, and this time stated so it can be re-derived rather than trusted: eventLogSchemaVersion and engineVersion are witnessed by era-key-stamped-v1 and both are run-document MUSTs; per-event schemaVersion and pinnedVersions are not asserted and carry no MUST in that document — checked against the prose rather than assumed to be a further gap, which closes the axis question rather than leaving it open. A docstring that describes coverage goes stale the moment coverage changes, which is the argument for stating what a reader can re-derive over what was true once.
  • v2 charter Phase 4 — the SECOND run-document stamping MUST, also unasserted, also violated. version-negotiation.md §Stamping requires engineVersion on every persisted run document, with an escape scoped to "legacy runs that predate the contract" — which cannot reach a run created seconds ago. Measured absent on the live tier-1 host, and the tier-2 host reported the same on a run minutes old. A grep suggested coverage that did not exist: three scenario files mention engineVersion, and none asserts it on a runversion-fold.test.ts reads it from the discovery document and wasm-pack-load.test.ts carries it only as a type field. That is the same trap as the eventLogSchemaVersion docstring one candidate earlier: the identifier is present, the requirement is not witnessed, and searching for the name answers the wrong question. Third leg added to era-key-stamped-v1, negative-controlled against the live host — 2 of 3 legs now fail there, which is the correct reading of a host that stamps neither field.
  • v2 charter Phase 4 — a v1 MUST that no scenario has ever asserted, and a docstring that claimed it did. version-negotiation.md §Stamping: "Every persisted run document MUST carry an eventLogSchemaVersion: number field. The current v1 value is 2." Neither production host stamps it, on any run either has ever served — both measured it independently, each after the other published its own greps. The reason nothing caught it is in the suite. version-negotiation.test.ts opened by claiming it checks "the four version axes (engineVersion, eventLogSchemaVersion, per-event schemaVersion, pinnedVersions) appear where the spec says they should" — and protocolVersion was the only axis asserted. Across all 444 v1 scenario files the sole occurrence of the identifier eventLogSchemaVersion was that sentence: a docstring describing a check that did not exist. run-snapshot.schema.json requires only runId/workflowId/status, so every snapshot ever sent validated cleanly. The consequence fails in the direction that punishes correctness: §Legacy detection classifies a run as legacy when the field is "undefined or < 2", and a legacy run is specified to have no event subcollection so "Readers MUST fall back to the snapshot" — a conforming client therefore ignores the event log the host is in fact serving, while a careless one reads it. New witness era-key-stamped-v1 (2 legs, unaided, major 1) and the docstring now states what it actually asserts and names engineVersion, per-event schemaVersion and pinnedVersions as still unasserted rather than implying coverage. A comment claiming coverage is worse than no comment: it answers "is this tested?" for anyone who greps, and answers it wrongly. Negative-controlled against the live tier-1 host — and the first version of the scenario used the unversioned /runs path, got a 404, and soft-skipped past a host that violates the rule, which is the vacuity failure the scenario exists to catch, committed by the scenario itself; the comment recording that is left in the file.
  • v2 charter Phase 4 — two governance rules about seam-gated evidence, and the Phase 4 exit gap they explain. Measuring the charter's Phase 4 exit against the deployed tier-1 host found v2-fork-a-v1-run passing while witnessing nothing: both requirement rows blocked. Two causes — the scenario is seam-gated and the host advertises no seams profile, and the host's bundle is --target-major 1 while both scenarios the exit criterion names are major 2, so they are absent, not failing. I proposed the obstacle was that production must not mount test seams. The host corrected the framing and the correction matters: the canonical /conformance/seams surface was never built there at all — its test hooks live in a host-extension namespace — so no revision can witness it and the gap is a build task, not deployment configuration. Two rules land in GOVERNANCE.md §"Acceptance evidence tiers": (1) a seam-gated requirement MAY be witnessed on a side revision when the seam is a precondition (seeding an era-2 log so there is something to fork — the fork implementation under test is production code on the same image, and host.build keeps the distinction visible), and MUST NOT be when the seam is in the path being asserted on, which would be a host measuring its own stub; (2) "the seam is not mounted" and "the seam does not exist" are different facts and MUST NOT be recorded as the same one — recording the second as the first turns an unwritten feature into a schedule. Runbook gains both, plus a lesson from the tier-2 host, which verified a claim of mine rather than taking it and found 3 of 4 cursor assertions failing rather than 2, then narrowed its own executedFail: 0: the danger of a green bundle is not that it lies, it is that its scope is invisible in the numberexecutedFail: 0 and inapplicable: 206 are the same sentence and only one gets quoted.
  • v2 charter Phase 4 — four lessons batched into the third-host runbook, all measured on live hosts. Held back rather than cut as separate candidates; under the P4-SPEC-17 applicable-set gate a bump adding no scenarios invalidates no host bundle, so this batch costs both hosts nothing. (1) Verify in the environment the artifact will RUN in — a change typechecked, booted locally, and would not have started the production image at all, because the package it resolved is a devDependency the runtime stage omits; a dev tree has devDependencies and the image does not, so that verification was structurally incapable of seeing the failure while looking exactly like verification that could. (2) Read the serving revision by traffic percentage, never by index or latestReadyRevisionName — which returns the most-recently-ready revision, a 0%-traffic tagged revision included. Two sessions reported the wrong revision from that field on one day, and one drew a correct conclusion from it by luck. Corollary: writing a secret is a promise; a revision serving it is the witness — a latest secret reference resolves once at revision start. (3) A requirement that lives only in a JSON Schema description has no witness and no MUST — the event sequence field said "first event is 0" since v1, no prose stated it, and nothing asserted it across the entire v1 line; one host was 1-based for the life of the product and could not have been told. Of two independent hosts, the one that was right was right by luck of implementation. (4) A parameter accepted and ignored produces the same silence as a requirement written and unasserted — a host with a permissive request schema accepted afterSequence and discarded it, returning 200 with a full replay on every poll, so a resuming client loops forever and nothing errors. The defence for (3) and (4) is identical and is not "write it down more carefully": assert the EFFECT, not the acceptance. A check on an effect cannot be satisfied by a permissive parser or by prose nobody reads.
  • v2 charter Phase 4 — RFC 0165's acceptance is re-grounded, and the steward's own error is recorded with it. On 2026-09-04 both hosts were fetched and neither served the surface RFC 0165's acceptance claimed; the acceptance cited merged PRs for a claim about a live wire. That finding produced GOVERNANCE's rule that a merge is a promise and only a deployment is a witness, and a revert to Active was prepared — and never opened as a PR, so it never landed. The RFC therefore stood at Accepted on merged-PR evidence for a further day while this steward repeatedly stated it did not. The status is unchanged because it is now earned: openwop-app revision 00691-hay serves protocolVersions [&quot;1.1&quot;,&quot;2.0&quot;], preferredVersion 1.1, signingKeys[] and eventLogSchemaVersion 3 live; its signed bundle v3 (witness 9420baa1ae9a, 1971 rows / 5741 assertions, signature resolving in the live discovery document) records the three gated scenarios at 34 / 30 / 20 assertions, all executed-pass; and the steward re-ran all three independently against the deployed host on a separate credential — 18 tests, executed-pass 19, including the run-creating legs that a rate-limit had made unreachable. MyndHyve's signed bundle carries the same three at 16 / 15 / 10 across four corpus versions. The error is written into the RFC's own Updated line rather than quietly closed, because the rule it violated is the one this RFC's remediation created.
  • v2 charter Phase 4, P4-SPEC-18 — an id assembled at runtime is invisible to every tool that greps for it. v2-bundle-signature-attributable (P4-SPEC-9/11, mine) built its four leg ids by template concatenation from a shared base — ` ${ID}.surface and siblings. v2-coherence-not-in-bundle` proves a coherence id is cited by searching the source for the literal string, so none of the four was visible to it and the check failed against a live host. Found by running the full v2 lane against the newly deployed tier-1 host with an independent credential, where it appeared beside seven other reds and would have been easy to file under "the identity PR is not merged yet". The ids are now full literals. The general form is worth more than the fix: an id you cannot grep is an id you cannot audit, and this corpus has several checks that work by literal search precisely because that is the cheapest way to prove a citation exists.
  • v2 charter Phase 4, P4-SPEC-17 — the cut gate asked "which version" when the question is "did you run what I now require". P4-SPEC-14 fixed a real defect (a bundle at rc.2 satisfied every host gate for a corpus at rc.15) by requiring suite.version to equal the corpus being cut. That was too strict in a way a tier-2 host measured: between its rc.8 and rc.16 bundles, the number of scenario rows added at --target-major 1 was zero — every candidate in that span added only v2- files, which do not run against a major-1 host. So the equality check rejected a bundle that had executed precisely the same applicable requirement set, charging fifteen minutes and real production runs for a different signature over identical evidence. With ten candidates cut in one day, corpus bumps were arriving faster than a full lane takes to run, so "certify last"* was not schedulable and the freeze could not be absolute either — real blockers kept surfacing. The gate now compares the applicable set: for a bundle at targetMajor N, every scenario file scenario-majors.json marks applicable at N must have a row in the bundle. The original guarantee is kept in full — verified as a negative control, the rc.2 bundle still fails against rc.18, and now names the four scenarios it never ran (v2-advertised-fixtures-exist, v2-advertised-path-space-served, v2-era-stamp-universal, v2-version-header-honored) instead of naming a version. It needs no checkout of the older corpus: the bundle's own rows plus the current corpus decide it. This bump adds no scenarios, so under its own new rule it invalidates no existing bundle — including the tier-2 bundle cut against rc.18 an hour ago. Proposed, with the measurement, by myndhyve-1.
  • v2 charter Phase 4, P4-SPEC-16 — the path-space rule was an INFERENCE, and the scenario cited a section that does not state it. P4-SPEC-15 shipped v2-advertised-path-space-served citing versioning.md §5. §5 says a host MUST advertise both majors, MUST emit the response header, and MUST serve /.well-known/openwop as one header-selected resource — it says nothing about the path space. §1.2 defines where v2 operations live ("an unversioned path is the v2 surface") but states no obligation to mount them. So the scenario asserted a rule the corpus never wrote, which is precisely the defect P4-SPEC-13 was a correction for, committed again two hours later. The rule is now written in §1.2: a host advertising a major MUST reach, under that major, every operation it serves under the other; advertising a major is a claim about the path space, not about the header-selected discovery resource alone; and the pairing is normative, because a lone 404 cannot distinguish "does not serve that operation" from "serves it and did not mount it under this major" and only the second is a defect. Citation corrected to §1.2. Found by auditing my own scenario's premise before it could fail a host on an unwritten rule — the same audit that would have caught P4-SPEC-13 before it shipped.
  • v2 charter Phase 4, P4-SPEC-15 — advertising a major is a claim about the PATH SPACE, and nothing checked it. v2-version-header-honored (P4-SPEC-6) verifies the header is honored or refused rather than ignored — but it probes /.well-known/openwop, the one resource whose representation the header selects. So it cannot see a host that negotiates correctly on the well-known resource and has mounted almost none of the rest of major 2. Measured live on the tier-1 host: it advertises protocolVersions: [&quot;1.1&quot;,&quot;2.0&quot;] and serves two of fifteen top-level segments of the v2 path space. Its unversioned mount is a deliberate allowlist — chosen over a blanket /v1-strip because the host serves a large non-/v1 surface a blanket rewrite would shadow, which is sound — and the list was simply incomplete; POST /webhooks under major 2 returned 404 while POST /v1/webhooks returned 201. Every probe used to call that dual stack live hit /.well-known, so every one of them passed. Found by the host itself, applying the artifact rule to a red it had first classified as a harness defect. Witness v2-advertised-path-space-served (unaided): for each parameterless GET in spec/v2/path-manifest.json, probe /v1&lt;path&gt; and &lt;path&gt; under major 2 — a PAIR, because "does not implement webhooks" and "implements webhooks but did not mount them under major 2" are different facts a lone 404 cannot separate. Verified both ways: passes the v2-reference host, and fails the live tier-1 host with 5 of 5 pairable surfaces at /v1 → 200, major 2 → 404.
  • v2 charter Phase 4, P4-SPEC-14 — the cut gate accepted evidence measured against a different contract, and could not tell. check-cut-gates.mjs exists to answer "is every §F predicate machine-true on the release candidate" — and it never compared the host bundle's suite.version to the corpus being cut. Measured 2026-09-04: a bundle at 2.0.0-rc.2 passed every host gate for a corpus at 2.0.0-rc.15 — thirteen candidates and nineteen corpus defects later — and looked identical to current evidence. That is not merely stale: later candidates added scenarios (the era-2 writer rule, version-header-honored, era-stamp-universal), so the old bundle never executed the checks the newer contract requires, and its clean executedFail: 0 said nothing whatever about them. The gate now requires suite.version to equal the version being cut and says why on mismatch. Verified both ways: the rc.2 bundle now FAILS the Front door, and a freshly re-cut bundle passes — 150 executed-pass where the stale one had 144, the six being exactly the scenarios added since. This also makes the RC freeze load-bearing rather than a courtesy: every corpus bump invalidates every host bundle, so cutting fewer candidates is now a measurable cost saving rather than a preference.
  • v2 charter Phase 4 — the runbook answers a governance question the tier-1 host raised: a merge gate that cannot pass mid-migration. The moment a host pins to the v2 suite, a conformance lane wired into its merge gate measures the whole v2 contract including surfaces it has not built — so nothing merges until the migration finishes, which blocks the migration. Ruled: do not run the merge gate with --require-behavior. That flag means "I claim to implement all of v2; fail me if I do not advertise it" — a claim a mid-migration host is not making, so asserting it is not strictness but a false statement about the host that the host then fails. behavior-gate.ts already says the default exists precisely so a host "doesn't suddenly fail the suite when new optional profiles ship". Merge gate without the flag (catches regressions in what exists); certification lane with it, or with OPENWOP_OPTED_OUT_PROFILES for deliberate non-implementation. Surviving reds get a named-scenario baseline that may only shrink — the ratchet shape this corpus already uses for docs/witness-baseline.json and the threat-model pointer count — and the baseline MUST name scenarios with reasons, never a count, because a count lets one red be swapped for another silently. The hazard is not a red gate; it is how someone eventually makes it green: quarantine is excused evidence wearing a gate's clothes. Also recorded, from the same host: a wrong check hides inside a set of correct failures almost perfectly — two suite defects were caught only because the v2-reference host has no legitimate failures for them to hide behind, and were invisible on the migrating host where they sat filed under "unimplemented v2 legs". A clean reference host is a control, not merely an example.
  • v2 charter Phase 4, P4-SPEC-13 — a scenario I shipped failed correct hosts, and the correction matters more than the fix. v2-advertised-fixtures-exist (P4-SPEC-4) shipped with a leg asserting that a host's advertised fixtures[] are a subset of conformance/fixtures/"the vocabulary is closed, so an id the corpus does not define is a typo or an invention rather than a capability." The vocabulary is not closed. Host-supplied fixtures are the normal case: 99 fixture ids the scenarios gate on are deliberately not shipped by the corpus, and v2-approver-enforced says so in its own docstring — it needs an approval fixture whose approversList names a principal the suite is not, and records blocked naming it because "no such fixture ships in conformance/fixtures/". So the leg failed a host for doing exactly what the corpus asks. Found by running the suite against the v2-reference host, which advertised two legitimate host-supplied fixtures and was marked non-conformant for it. Set membership cannot distinguish a typo from a legitimate host fixture, and a check that cannot tell those apart is not a check — it is a coin flip that lands on "fail" for correct hosts. The leg is deleted. What survives is the one that was always sound: an advertised fixture MUST be creatable, which holds whoever defines it and is what catches the drift the misattribution comes from. My P4-SPEC-4 entry called the deleted leg "a stronger and different check"; it was neither. Also in this cut: v2-era-2-append-vocabulary now reports blocked when seedEra2Log returns success and the log reads back empty — the seam's return value is a wrapper claim and the readable log is the artifact, and asserting against an empty log charged the writer rule for a seam defect.
  • v2 charter Phase 4, P4-SPEC-12 — @next can name an exact-peer pair that was never published together, and the refusal said the wrong thing about it. The suite and @openwop/spec-artifacts are declared EXACT peers, published by two jobs in one workflow — so the next dist-tag moves per package, and for the minutes between them @next resolves a mismatched pair (measured live: spec-artifacts at rc.11 beside conformance at rc.10). The suite correctly refused, but it refused with the digest-mismatch message — "the vendored contract is not the one this suite shipped … Reinstall the package; do not hand-patch vendored contract files" — which describes a corrupted install, and sends the reader to debug damage that does not exist. peer-version is now its own verdict kind: it names both versions, says plainly that nothing is corrupt, and states the remedy (install both at the same explicit version, never at a dist-tag that moves per package). It still refuses to run — a skewed peer must not execute — but it refuses with the fault it actually found. Found by a tier-2 host that hit the window while re-pinning and warned the other host inside two minutes, then retracted the warning explicitly when it cleared — noting that "an unretracted warning is the same failure as an unverified claim, just pointed the other way." Its durable rule is now in the runbook: pin both peers to the same explicit version and confirm both resolve before installing. Also recorded: a negative result measured during a known propagation window is not evidence at all — that host checked twice, twenty seconds apart, and the package resolved on the third look.
  • v2 charter Phase 4 — the third-host runbook (docs/runbooks/V2-HOST-MIGRATION.md, non-normative). The charter's stated reason for migrating two hosts is that "the second is a rehearsal of the first and a third host gets a runbook." This is that runbook, written from what the two migrations actually cost rather than from what the plan predicted. It carries the ordering that an adversarial review corrected (the wire lands FIRST, because every v2 scenario reaches the host through v2 discovery and nothing can be witnessed before it), the era-key rules including the writer rule that only surfaces on a host whose runs straddle the deploy, the identity projection that avoids re-minting ids, and the certification steps in the order that does not strand a key. Its central section is "verify the artifact, not the wrapper" — three measured failures of cmd; echo "EXIT=$?" in one working day (exit 2 reported as 0, a SIGKILL at 137 reported as 0, and an exit 1 that nonetheless wrote a valid bundle), which is wrong in both directions. Its organizing idea is the one failure mode that recurred five times across the phase: a signal that looks identical whether it is true or false — a 200 that honored the header versus one that ignored it; an inapplicable that means absent versus excused; a negative control that fired versus one that tested the wrong file; a signature that attributes versus one signed by a keypair minted seconds earlier; and a document that is specified versus one merely not-yet-forbidden by an open root.
  • v2 charter Phase 4, P4-SPEC-11 — I put signingKeys[] on the wrong root, and a tier-2 host asked the question that found it. P4-SPEC-9 added the bundle-signing key list to the v2 discovery root only. But a certification bundle is v3 regardless of major: under RFC 0168 §D.3's one-package decision, a v1 host measured by a 2.x suite at --target-major 1 emits a v3 bundle whose signature needs attribution exactly as much as a v2 host's does — and a v1-only host has only a v1 document to hand a verifier. So the fix I shipped to close the unattributable-signature gap reintroduced it one major down, for every v1 host. Added to schemas/capabilities.schema.json as an OPTIONAL v1.x additive root key, the same shape RFC 0179 used for root preferredVersion; the Front-door gate already resolves the key at the document root and needed no change. Witness leg …bundle-signature-attributable.v1-root. Found because MyndHyve published the key on its v1 root and then asked whether it was allowed to — its root is additionalProperties: true, so the document validated and was unspecified, and nothing in its pipeline could tell those apart. openwop-app's closed v2 root refused the same act and said why. One host got an answer and the other got silence that looks like an answer — the fifth instance this phase of a signal indistinguishable from its own negation, and the first where a closed schema is what paid.
  • v2 charter Phase 4, P4-SPEC-10 — seven defects the tier-1 host found while implementing v2 identity, and two of them were the corpus telling hosts the opposite of the truth. (1) schemas/v2/run-event.schema.json's $comment said "RunEventDoc is a SERVER-EMITTED shape, so it is open (additionalProperties: true)" while the file set false — v1 text carried into a v2 file. A host that believes the comment emits root-level extension fields and fails v2-event-type-closed; the keyword is the contract and a comment is only a claim about it. (2) run-snapshot.schema.json carried the same contradiction ("readers MUST ignore unknown fields" beside a closed object) plus a /v1/ path in a v2 schema. (3) A dead 117-entry twin lived at run-event.schema.json#$defs.RunEventType — nothing $refed it, but run-event-payloads.schema.json still cited it as the authority, and 36 of its entries were exactly the v1 spellings the v2 rename retired (run.resuming, agent.promptResolved, replay.divergedAtRefusal, …; verified against event-codemap.json — 36 renamed rows, 36 stale entries, none present in the v2 vocabulary). The stale twin is what a hand-editor finds first. Deleted; the citation repointed at the generated branch. (4) The closed v2 RunSnapshot had no seat for eleven fields the v1 wire actually carried. The v1 schema was additionalProperties: true, so it never had to declare what hosts served; closing the object in v2 made the omission visible as data loss — a fork's parentRunId and a run's inputs became unobservable from getRun. Both are now declared, and an extensions seat keyed <org>.<name> is added: closing an object does not make hosts stop having extra state, it makes them delete it or serve an invalid document, and every other closed v2 surface already had this seat. (5) ids.schema.json#/$defs/tenantId could not express an anonymous tenant — it excluded : while RFC 0132 anon tenants are anon:<hash> and RFC 0170 §A.5 makes the anonymous surface first-class, so a conforming host could serve an anonymous run with neither a valid owner.tenant nor a valid tenant-bound runId. Ruled: admit anon: and only anon: (: is a legal path character per RFC 3986, so the <tenantId>/<opaque> run-id form stays safe). Renaming anon tenants at the cut was rejected — it is a write to historical identity, the one thing this migration does not do. (6) persistence.md's token drain was written over a segment count ("v1 two-segment tokens"), but the v1 {token} path parameter carries no pattern, so a conforming v1 token may be a single opaque row key and such a host had no drain rule at all. Restated over the ow2. prefix. (7) v2-interrupt-token-scheme was narrower than the registry it cites: its 404 leg demanded exactly not_found while spec/v2/errors.json also registers interrupt_not_found for that state, so a host answering the more precise registered code failed. Both now accepted. Found by openwop-app implementing RFC 0170 (openwop-app #3639).
  • v2 charter Phase 4, P4-SPEC-9 — a bundle signature nobody could check, and the disposition that said otherwise. RFC 0168 recorded the objection verbatim — "an Ed25519 attestation without a key registry is a signature nobody can check" — and disposed of it by naming signingKeys[] in the host's discovery document. That surface did not exist. The disposition named a place, nothing built the place, and nothing noticed, because the gate that depended on it never looked: check-cut-gates.mjs tested the attestation with typeof signature.sig === 'string', which any string satisfies. It could not tell a host key from a keypair minted seconds earlier by whoever wrote the bundle — and that is precisely what both Phase 4 hosts did, independently, minutes apart, each publishing the key nowhere. Both reported it themselves rather than letting it through, which is the only reason it was caught before the cut and not after. Fixed in both halves, because either alone restores the failure: signingKeys[] is now a v2 discovery root key (closed entries, required keyId/alg/publicKey, optional retiredAt — a retired key MUST stay listed, since dropping it silently invalidates every bundle it already signed), and the Front-door gate resolves signature.keyId there and verifies the Ed25519 attestation under the published key. The gate now reports four outcomes that the presence check collapsed into one: no discovery document read (blocked — the attribution evidence was not read, which is not the same as absent), the host publishes no keys, the id is not among those published, and the attestation does not verify. Verified as a four-way negative control: a signature verifies under a published key; fails when the host publishes none; fails when the id is unlisted; and fails when one field is altered after signing — that last case passed before this change. Witness v2-bundle-signature-attributable (3 legs, corpus), itself negative-controlled by regressing the gate to presence-only and confirming leg 2 fails. The general shape, for the fourth time this phase: a check whose negative result is indistinguishable from success.
  • v2 charter Phase 4, P4-SPEC-8 — the v2 bundle schema could not express a prerelease suite version. schemas/certification-bundle-v2.schema.json pinned suite.version to a three-part-only pattern, which predates RFC 0168 §D.3's one-package decision. Under that decision the 2.x suite measures v1 hosts too via --target-major 1, so a v1 host measured by a prerelease 2.x suite legitimately emits a v2 bundle whose suite.version is 2.0.0-rc.N — and the schema rejected it, making that bundle unvalidatable. Widened to admit a SemVer prerelease. Found by the tier-1 host trying to certify while pinned to the RC line. Its wider conclusion does not hold, and the correction is recorded because it matters more than the finding: the §G.2 host gates are NOT structurally unreachable. They read a bundle v3, whose schema has no version pattern at all, and --certify emits v3 by DEFAULT — the v2-reference host has been emitting schema-valid v3 bundles at 2.0.0-rc.N throughout. A lane hitting this is emitting the deprecated v2 bundle, which RFC 0168 §E.3 says stops substantiating anything at v1 end-of-support.
  • v2 charter Phase 4, P4-SPEC-7 — the v1 wire of an era-3 log, and a contradiction between two of the suite's own scenarios. persistence.md wrote the era contract for a v2 reader of an era-2 log and never said what the v1 representation of an era-3 log is. Through the overlap a host serves both majors and /v1/… keys are unchanged, so the mirror case is forced: a run created today is era 3, stored in v2 vocabulary, and must still read on /v1/… exactly as before the cut. Ruled: map back through the same codemap row, inverted — the map is a bijection (118 rows, 118 distinct names on each side), a host MUST verify that at load, and MUST refuse to serve the v1 representation rather than guess if a future row folds. Both alternatives are named and rejected in the prose, because they are the same change with the honesty removed: storing v1 spellings under an era-3 stamp makes the stamp a lie that the closed-enum scenario passes by luck, and serving v2 names on /v1/… breaks the wire the overlap exists to preserve. Found by the tier-1 host implementing the era key. Separately, v2-era-stamp-universal (mine) described advertising era 2 as "legitimate through the overlap" while v2-era-key hard-requires 3 — a host taking the first at its word fails the second. The two now own different claims: era-key owns must be 3, era-stamp-universal owns the advertised value agrees with what the writers write.
  • v2 charter Phase 4, P4-SPEC-6 — OpenWOP-Version is honored or refused, never ignored. A host that does not implement v2 has two correct answers to the header: serve the v2 representation, or refuse with 406 protocol_version_unsupported. The third thing it can do — return 200 with the v1 document, ignoring the header — is invisible to every presence-gated scenario in the suite, because an absent v2 shape records inapplicable whether the host REFUSED or silently handed back v1. Both are non-failures, so a bundle looks clean while witnessing nothing. That is the shape that let RFC 0165 sit Accepted on a host serving none of it. Measured on the live tier-1 host 2026-09-04: OpenWOP-Version: 2 returned 200 with a body byte-identical to the header-less fetch. Witness v2-version-header-honored (2 legs, unaided, no v2 support required to run): fetch the resource twice and compare. Verified as a negative control — it FAILS against the live host today and passes against a host that refuses. Probe credit to the openwop-app session.
  • v2 charter Phase 4, P4-SPEC-4 — an advertised conformance fixture is a claim, not a wish. fixtures[] in discovery gates whether a scenario runs at all, but nothing checked that an advertised id names a fixture the corpus defines or that the host can serve it. The failure mode is misattribution: a listed-but-unseeded fixture makes some OTHER scenario fail on a run that cannot be created, and the failure is charged to that scenario's requirement rather than to the advertisement that was wrong. Prompted by a suspected 46-vs-47 drift in a tier-2 host, which that host then verified and retracted — its seeded and advertised sets are identical, and the original count came from an eyeballed array literal rather than a measurement. The retraction is recorded here rather than quietly dropped, because an unverified count treated as evidence is the exact failure this program exists to catch, and a scenario justified by a false witness is worth less than one justified honestly. The scenario stands on its own: leg 1 checks advertised ids against the corpus catalog, which is a stronger and different check than seed-versus-discovery parity, and the misattribution failure mode is real independent of any host — a listed-but-unseeded fixture makes some OTHER scenario fail on a run that cannot exist. No host is currently known to exhibit it. Witness v2-advertised-fixtures-exist (2 legs, unaided): the advertised set is a subset of the corpus catalog, and a deterministic bounded sample is creatable.
  • v2 charter Phase 4, P4-SPEC-3 — three era-key rulings a tier-2 host asked for (persistence.md, no wire change). MyndHyve's migration review found one creation path writing era 2, another writing nothing, and discovery advertising 1 — a value no path wrote — and asked three questions the corpus did not answer. Ruled: (1) absent stays era 2 forever and is never backfilled; the trichotomy is sound only because a v2 host stamps 3 on every creation path in one change, since a path left unstamped after the cut produces runs indistinguishable from pre-cut ones and every reader translates them as era 2 — a silent wrong read, not an error. (2) Collapsing to one constant is a precondition for advertising, not a consequence: a host whose writers disagree has no single value to advertise and whatever it publishes is false for some of its own runs. (3) The snapshot era key is required on the wire and MAY be synthesized from absent-⇒-2; a missing stored era is not a read error — and, said plainly, that makes the field unable to falsify era handling on its own, which is why the reader and writer rules carry the obligation and this field only reports it. Witness v2-era-stamp-universal (2 legs, unaided).
  • v2 charter Phase 4, P4-SPEC-1 — the era-2 writer rule (persistence.md, no wire change). The reader rule translates an era-2 log at the storage boundary, which is only coherent if the log stays in one vocabulary — but nothing said what vocabulary a writer uses when appending to a run created before the upgrade. A host that starts writing v2 names into an era-2 log breaks its own reader two ways: a renamed type is mapped twice, and a v2-only name is not on the codemap's v1 side at all, so the read fails with event_type_unmapped. The era key is now stated to be fixed at run creation and to fix the log's vocabulary for the run's lifetime. Found by the Phase 4 architecture review, not by a host outage: openwop-app suspends runs on human approval, so runs will straddle the deploy, and draining them first is explicitly not the path (persistence.md §"Runs pinned to v1"). Witness: v2-era-2-append-vocabulary (2 legs, seam-gated), registered in RFC 0176's falsifiability table.
  • v2 charter Phase 3 — the 2.0.0-rc.1 release candidate. RFCs 0169 (discovery and capabilities), 0174 (governance) and 0178 (assurance registers and deprecation machinery) flip Active → Accepted on the corpus-gate evidence tier; scripts/check-accepted-predicate.mjs (RFC 0174 §B.1) passes on all three, and no host tier is claimed — the openwop-app bundle is a Phase 4 entry item, which is why the other eight children stay Active. scripts/check-cut-gates.mjs renders the charter's ten §F predicates with the evidence file each read: seven are corpus properties and pass unaided, and Witness, Coexistence and Front door read a host bundle v3 and report blocked without one. The Coexistence gate passes on the v2-reference example host — a host implemented from spec/v2/core/ rather than from v1 host code, which is what the Front-door predicate asks for and what found seven corpus and six suite defects along the way.
  • v2 charter Phase 3, P3-E3 — the v2 scenarios (suite 2.0.0-rc.0 → 2.0.0-rc.1, no v1 wire change). 51 v2- host scenarios (RFC 0169–0177: closed discovery root, header-selected well-known, negotiation, Subject/subject-link, id grammar, interrupt tokens, error registry, payload/event closure, afterSequence poll cursor, effect-seam manifest, compensation/effect projections, webhook durable delivery, relaxations, revocation, assurance downgrade, era key + v1-event translation + fork-a-v1-run + pinned-run disposition, v1-signed webhook acceptance, manifest engines ceiling, peer-dependency declaration, chain pin, bundle v3) and 15 src/coherence/v2- corpus wrappers, every assertion a req() id; scenario-majors.json 494 files (v1 444 / v2 51); the two RFC 0176 seams (seedEra2EventLog, receiveWebhookDelivery) enter api/seams-v2.yaml via derive-v2-api.py; 9 SECURITY/invariants.yaml rows (7 protocol-tier with v2 witnesses, 2 demoted reference-impl/unwitnessable per RFC 0170 §E.2 — witness baseline 19 → 21, recorded); requirement ids appended to the falsifiability rows of every child; evidence/corpus-ledger.json regenerated at 2.0.0-rc.1.
  • v2 charter Phase 3, P3-E2 — bundle v3 signed, the seams profile driver, the v2 request header (suite 2.0.0-rc.0, no v1 wire change). conformance/src/lib/certification-bundle-v3.ts (RFC 0168 §E): witnessDigest over the reporter record, Ed25519 signBundleV3/verifierSign over the canonical JSON of {witnessSha256, host.build, suite.version, discovery.sha256}, and verifyBundleV3 that recomputes the digest, the totals and assertionCount, requires detail for any non-pass, host.build, a well-formed signature (verified when the host public key is given), refuses an independent claim without a verifier signature under a key distinct from the host's (RFC 0148 R5), refuses a certified profile beside a recorded relaxation (RFC 0173 §A.2) and certifies nothing when blocked > 0. --certify emits v3 by default (--bundle-version 2 deprecated; v1 gone per §E.3) and needs --host-build <kind>:<id>, --signing-key <pem>, --signing-key-id (or the OPENWOP_HOST_BUILD / OPENWOP_BUNDLE_SIGNING_KEY[_ID] env), --evidence-tier self|steward|independent (independent adds --verifier-key/--verifier-key-id), records OPENWOP_HOST_RELAXATIONS into host.relaxations[], embeds the spec-artifacts lock into suite, self-verifies with the derived public key and validates against schemas/v2/certification-bundle.schema.json (which gains verifierSignature, required when a claim is independent). lib/seams.ts (RFC 0168 §C): under --target-major 2 the driver rewrites the three v1 seam prefixes to /conformance/seams/… (api/seams-v2.yaml) so the 165 seam-using scenarios keep working without a sweep, and sends OpenWOP-Version: 2.0 on every request unless the scenario set one (RFC 0172 §A.3); the seams profile is advertised through the conformance METADATA key (conformance.seamsProfile), never a capability flag — the RFC 0168 §C.1 / RFC 0169 §C.1 reconciliation, recorded against RFC 0168 at its flip. Self-tests for both.
  • v2 charter Phase 3, P3-E1 — suite 1.163.0 → 2.0.0-rc.0 (RFC 0168 executed; the 1.x line continues on release/1.x). (1) req() is the only assertion form (§A.1): every driver.describe( in the 444 scenario files became req('<id>', section, requirement) with the id the per-it ledger already derived, so no bundle id changes; the 23 interpolated titles carry hand-minted ids; req() now returns the same [impl@version] section: requirement message; scripts/check-req-only.mjs (stage 1) fails on a driver.describe, an un-id'd assertion message, a bare return in an it body (the RFC 0148 G8 unclassified return — the ones found were classified), or a duplicated explicit id; conformance/requirements.json reports explicitIds = every test. (2) @openwop/spec-artifacts (§D.2): spec-artifacts/ is the contract package — api/, schemas/ (v1 + v2), spec/v1/*.json, spec/v2/**/.json, CORPUS-STAMP.json — GENERATED and committed by scripts/generate-spec-artifacts.mjs --write/--check (225 files); the suite declares it as an exact-pinned peer, no longer vendors api//schemas/, embeds dist/spec-artifacts.lock.json at pack time, resolves the installed peer through Node's resolver (lib/paths.ts) and refuses to start when the peer's version or stamp digest differs (lib/corpus-stamp.ts verifyPeerContract); the tarball keeps only schemas/CORPUS-STAMP.json for hosts' provenance reads; check-published-suite-identity.mjs --package spec-artifacts, check-npm-pack-contents.sh, check-published-layout.mjs, openwop-check-publish-metadata.sh (EXPECTED_SPEC_ARTIFACTS_VERSION + exact-peer check) and openwop-publish.yml (a publish-spec-artifacts job on v / openwop-spec-artifacts/v tags; pre-releases publish under the npm dist-tag next) know the second package. (3) --target-major 1|2 (§D.3): one package; conformance/scenario-majors.json (generated) names each scenario's majors (v2- → 2; fixtures-valid both; the rest 1); the CLI resolves the default from the host's preferredVersion (RFC 0179), else max(protocolVersions[]), else 1, passes the selected files to vitest, sets OPENWOP_TARGET_MAJOR, and now honors --filter under --certify; targetConfigurationSha256 hashes the resolved base URL (it hashed undefined when the URL came from the environment). (4) Corpus-coherence out of host bundles (§D.1): the 29 scenarios moved to conformance/src/coherence/ — the set is the directory listing (four hand-kept copies retired: the spec-coherence.ts list, the package.json negations, spec-coherence-scenarios.json, the registry self-test's list); scripts/check-spec-coherence.mjs runs them under vitest.coherence.config.ts with the per-it ledger and emits evidence/corpus-ledger.json, the "corpus gate — no host tier" evidence check-accepted-predicate.mjs reads; the suite self-tests run under vitest.selftest.config.ts; a host run never executes either. (5) Release lines: release/1.x cut from main at 7c81ace8; the spec and PR-check workflows run on release/**; openwop-publish.yml refuses a tag on neither main nor release/1.x; PUBLISHING.md §"Release lines from suite 2.0.0". Bundle v3 emission/signing, the seams driver and the v2 scenarios follow in P3-E2/E3.
  • v2 charter Phase 3, P3-D — spec/v2/core/ prose, spec/v2/ext/, the two threat models, and the 43 prose gap tables absorbed (no packed content touched). Twenty core documents under the RFC 0174 §E.2 budget (check-core-budget.mjs: 20,8xx of 25,000 words, wc -w on raw markdown, generated headers.md included): overview (axioms in force, §0 closed-enum growth rule, RFC 0174 §B.4 restated), versioning (0172/0179), capabilities (0169 + one ### § <key> anchor per core family — check-declaration.mjs reads them), identity (0170), idempotency, runs, events, errors (the 92-code table generated from spec/v2/errors.json), headers (generated), interrupt, replay, persistence (0176), security-defaults (0173 obligation table), webhooks, interop (0175), packs + connection-packs + form-content-packs + workflow-chain-packs (0177), conformance (0168). Every rule traces to a child RFC sentence or a v1 MUST no child retracts; the drafters' unhonorable sentences are recorded in scratchpad-free form as register follow-ups (codes the v1 prose cited but no registry carried — idempotency_in_flight, forbidden, run_terminal, interrupt_not_found, interrupt_cancelled, mock_provider_forbidden, force_engine_version_forbidden, envelope_correlation_conflict, sub_chain_cycle, sub_chain_depth_exceeded — are now spec/v2/errors.json rows; negotiation.decided is a decided codemap row with a closed payload; interruptResolved carries resolvedBy/decision and runPaused carries drainPolicy per the v1 MUSTs; bundle v3 durability is session | deployment | persisted and the effect-seam manifest row is { seam, kind, guarded, guardedBy, branchReFires? } exactly as RFC 0173 §A.2/§C.1 state them; replay modes is replay | branch). spec/v2/ext/: the 13 ext-anchored families with witness: / technical: / adoption: headers, grpc-transport/ (demoted; non-normative proto), portability/ (goals/export/import decided out of api/v2, RFC 0174 §E.2 → C.1), sandbox-runtime-notes/, provider-idempotency/registry.json. SECURITY/threat-model-interop.md (RFC 0175 §F.1; the file two Stable documents cited that did not exist) and threat-model-replay.md §6–§8 (RFC 0173 §E.2); check-threat-model-template.mjs now strict in stage 10. RFC 0174 §E.3: the 43 spec/v1/*.md "Open spec gaps" tables (196 rows: 132 id rows, 42 id-less table rows, 22 bullets) are spec/v1/spec-gaps.json, hand-classified (open 107 · closed 69 · carried 20; witness 0 unclassified), merged into spec/v1/gaps.json by generate-gaps.mjs (801 gaps, one namespace) and each table retired to a pointer; four rows the corpus had already resolved are closed with the evidence. GOVERNANCE §"Acceptance evidence tiers" gains the label corpus gate — no host tier (RFC 0168 §D.1). check-witness-classes.mjs --update-baseline no longer drops other scripts' ratchets (selfCarried restored to 342).
  • v2 charter Phase 3, P3-C — api/v2/ and the seams profile document (in construction; no packed content touched). scripts/derive-v2-api.py (PyYAML; --write/--check) derives api/v2/openapi.yaml (51 operations on unversioned keys over a bare origin — RFC 0172 §A.2; every operation takes OpenWOP-Version and every response declares it — §A.3/§A.4; X- headers renamed to OpenWOP- and Capabilities-Etag gone — RFC 0171 §C.1; the poll cursor is afterSequence with omission = from the first event and the one-meaning response shape — §E.2; the interrupt token path parameter carries the ow2. grammar accepting the v1 form through the overlap — RFC 0170 §E.1; the two one-member enums are the generated error envelope — RFC 0171 §B.1; the three RFC 0173 read surfaces GET /host/effect-seams, GET /runs/{runId}/compensation, GET /runs/{runId}/effects with new compensation-projection and effect-ledger-projection schemas; GET /host/events as the documented default hostEvents address), api/v2/asyncapi.yaml (server pathname empty, one runEvents channel whose address is the OpenAPI key with streamMode as a pattern over the closed set, hostEvents with a real address — RFC 0171 §E.1), api/seams-v2.yaml (RFC 0168 §C: the six seam/test-mode path keys the canonical document used to carry, under /conformance/seams/…) and spec/v2/path-manifest.json (operations + channels). spec/v2/release.json is the one release identity info.version reads (RFC 0172 §D.1). Stage 10 gains the derivation --check, both Redocly lints (api/v2/redocly.yaml keeps the two v1 suppressions with the reason recorded against RFC 0178's acceptance), the AsyncAPI validation and the v2 leg of check-path-parity.mjs (43 keys, proto leg retired-by-0175). Editorial forward pointers on the three RFC 0172 retractions in rest-endpoints.md §Versioning and grpc-transport.md (the v1 text stands; api/asyncapi.yaml's pathname: /v1 is packed content and gets its note with the next 1.x bump).
  • v2 charter Phase 3, P3-B — schemas/v2/ wire schemas (in construction; no packed content touched). scripts/derive-v2-schemas.mjs seeds 85 schemas from the v1 $ref closure ($id under /spec/v2/, closure by construction: declared properties close, declared free maps are explicitly open, the RFC 0138 hatch preserved and widened to ^(openwop-|x-|vendor\.) on pack-authored documents per RFC 0171 §C.2, id fields $ref ids.schema.json, engineVersion integer, bundle consts per RFC 0172 axis 10) and never overwrites a file a child has hand-edited. The children's decisions landed by hand: ids.schema.json (RFC 0170 §D.1, tenant-bound kinds with minted), subject.schema.json v2 (lanes session/anonymous, kind ⇔ lane for anonymous, keyClass iff SAML/SCIM, the actor chain bounded at four by $defs), subject-link.schema.json, run-snapshot (owner.subject required, eventLogSchemaVersion required ≥ 2, principal gone), run-event (type oneOf closed enum ⊕ positive vendor pattern, schemaVersion required), run-event-payloads (root and every definition closed; _typeIndex normative and generated from spec/v2/event-codemap.json — all 117 rows decided; the seven legacy interrupt payloads are $refs to the unified interruptRequested/interruptResolved per §A.4 E4), spec/v2/errors.json (82 codes: the 40 of rest-endpoints.md, the OpenAPI one-member enums, the pack and connection codes, and the 19 minted by RFCs 0169–0177 with statusSource recorded) → generated error-envelope.schema.json (scripts/generate-error-envelope.mjs), configurable.schema.json (RFC 0171 §D.1 closed nested shape; run-options and the capabilities metadata $ref it), certification-bundle.schema.json v3 (RFC 0168 §E: closed, witnessSha256/assertionCount/host.build/per-profile tier and count/signature required, detail conditional, host.relaxations[]), effect-seam-manifest.schema.json (RFC 0173 §C), pack schemas (signing.scheme const, keyId only, kind required, engines grammar with an explicit ceiling, versionDeprecated), debug-bundle seq gone (C4.7), and spec/v2/facets/*.schema.json — the hand-decided facet shapes for a2a, mcp, auth (lanes with issuers/revocation/assurance/proofs), replay (no suppression flag; the manifest path), webhooks, interrupt, sandbox, heartbeat (deliveryChannel), aiProviders (providers[], generation facets) that the generator now reads. New gates in stage 10: check-v2-schemas.mjs (Ajv 2020 compile inside the v2 tree, $id, closure with exactly the three vendor grammars, no supported on a family) and generate-error-envelope.mjs --check.
  • v2 charter Phase 3, P3-A — foundations for spec/v2/ (in construction; suite 1.162.0 → 1.163.0). After the Phase 3 plan and its /architect pass (eleven blocking findings absorbed; the RC-vs-GA split: Phase 3 ends at v2.0.0-rc.1 with the in-memory example host as executed-pass Witness/Coexistence evidence, openwop-app's legs at Phase 4; RC flip set {0169, 0174, 0178}): RFC 0179 Active — root preferredVersion (v1.x additive; the half RFC 0172 §A.1 said would be filed separately; the only packed change, hence the bump); spec/v2/declaration.json + schema (RFC 0169 §B: every v1 root key with an anchor core \| ext \| deleted — 71 core, 2 ext, 2 deleted, 17 metadata; planned: ids until P3-E) and spec/v2/README.md; scripts/generate-from-declaration.mjs (schemas/v2/capabilities.schema.json, spec/v2/profiles.json, peer-dependency-aliases.json; --write/--check), check-declaration.mjs, check-core-budget.mjs, check-path-parity.mjs, generate-deprecation-annotations.mjs, check-removal-dates.mjs, check-accepted-predicate.mjs, check-threat-model-template.mjs — wired as openwop-check.sh stage 10; the 1.x tarball never carries the v2 tree (pack-vendor.sh prunes schemas/v2, api/v2, api/seams-v2.yaml before the digest walk); spec-corpus-validity's v1 leg skips schemas/v2/; generate-cross-repo-evidence.mjs records the registry's distinct peerDependencies keys (the committed inventory check-declaration.mjs reads instead of a sibling checkout); PROTOCOL-STATUS lists the 18 version axes incl. OpenAPI/AsyncAPI info.version; advisory tier documented in SECURITY/invariants.yaml's header; witness baseline unwitnessable 20 → 19; the umbrella's openwop-cli row corrected (no SDK coupling) and the decision recorded (frozen v1-only). Cross-repo P3-0: openwop-sdks #34 and openwop-registry #46 pin their vendored-sync guards to CORPUS_TAG (1.162.0) and re-vendor from the tag (the registry's node-pack schema had lost the RFC 0138 hatch). Accepted 158 · Active 17 · Draft 1.
  • RFC 0168 Draft → Active — v2 child C.1 (evidence and conformance); v2 charter Phase 2, PR F — and the Phase 2 exit (RFC 0167 §G.1). The instrument every other child's witness runs under: req() becomes the only assertion form with a per-it ledger and a reword-without-alias CI failure (the Phase 1 helper exists with explicitIds: 0 and an empty alias file); the witness verdict is required on every family, requirement and invariant, unwitnessable needs a rationale and fails on a protocol-tier row (55 unwitnessable and 37 tests: [] measured); the six things v1 admits it cannot certify each map to one class and blocked retires as a bundle disposition; a seam-gated MUST mints an observation path or is demoted; host-sample-test-seams.md becomes the profile openwop-conformance-seams-v2 with its own api/seams-v2.yaml, forbidden from the capability namespace (its two testSeams flag proposals closed by deletion) and its nine operations evicted from the canonical OpenAPI and the 56-operation SDK path manifest; the 29 corpus-coherence scenarios leave host bundles for the spec repo's CI; the suite publishes dist/fixtures/vectors only with the corpus as the digest-checked peer dependency @openwop/spec-artifacts (the 1.138.1 mechanism removed); suite 2.0.0 is one package with --target-major and a shared scenario-id namespace; bundle v3 has a closed root, witnessSha256/assertionCount/detail/host.build/per-profile tier and count required, and an Ed25519 attestation with independent requiring a verifier key distinct from the host's (RFC 0148 G4/R5, RFC 0154 G4). Row C1.1 re-kinded retype → behavior (a codemod would fabricate evidence). v1.x fix: the CLI's --bundle-version help said "Default 1" while the code sets 2 (packed content: suite 1.161.0 → 1.162.0). Phase 2 exit: all eleven RFC 0167 children Active; check-codemods --at-active, alias coverage and migrations green. Accepted 158 · Active 16 · Draft 1.
  • RFC 0176 + RFC 0177 Draft → Active — v2 children C.9 (persisted data and coexistence) and C.10 (registry, packs, extension tail); v2 charter Phase 2, PR E. C.9 is Axiom 6's surface: the C.4 rename of 36 persisted, indexed, unique-keyed event types is applied by the codemap shipped as data in the suite and a reader rule keyed on eventLogSchemaVersion with absent ⇒ 2 (the tier-1 host persists no such column) and v2 writing 3, sequence space preserved including 0, unmapped types refused (event_type_unmapped) rather than tolerated; the adapter's seat is the storage boundary (openwop-app storage.listEvents, whose wrapper 34 call sites bypass; MyndHyve fromFirestore); a v1-pinned run continues under the adapter or is cancelled v1_pin_unsupported / cancelledBy: v2-cutover (V5 closed; MyndHyve's LEGACY_RUN_CANCELLED is the precedent); /.well-known/openwop is one resource whose representation the RFC 0172 header selects (the charter's per-major sub-objects deviation recorded), with removalTrigger (new optional register field) on the wrapper, the dotted mirror, Capabilities-Etag and MyndHyve's never-registered /.well-known/wop; every openwop-app table and MyndHyve collection gets a disposition; the corpus-tag pin becomes a MUST with the honest count (1 of 3 consumers, plus an unpinned drifted registry). C.10 reads the immutable data: 136 of 282 published versions pin <2.0.0, 146 have no ceiling, so an absent ceiling reads <2.0.0, pack_engine_unsupported binds at install on every publication path (the mirror included), and registry/v2/ is a parallel re-signed tree (a signed overlay is rejected — signatures authorize by namespace and the mirror never reads it); peer-dependency identifiers are the C.2 declaration-file keys with a generated alias table for the four grammars in the wild (host.fs/fs, openwop.agents.memoryBackends, facet paths, host.workspace); signing.method's two conventions that sign different bytes become one signing.scheme; publicKeyRef and absent kind go; the RFC 0138 hatch lands on agent-manifest and prompt-template (G4 decided); provider-id precedence (RFC 0095 UQ4) is fail-closed conflict plus a qualified form; chains pin exact versions (WCP2), children are reference-counted (WCP5), WCP4 is a named v2.x follow-up, forms reuse the edge-condition grammar and i18n.md (RFC 0137 G1/G3). RFC 0170's persisted-data row corrected (openwop-app has no metadata.owner). 27 migration rows; five proposed register rows; three new codemods. Adversarial reviews recorded. Windows waived. Accepted 158 · Active 15 · Draft 1.
  • RFC 0173 + RFC 0175 Draft → Active — v2 children C.6 (security defaults) and C.8 (transports and embedded protocols); v2 charter Phase 2, PR D. C.6 applies RFC 0164's own ruling ("opt-in security is the pattern the corpus keeps regretting") to the whole corpus: the fourteen auth-family gate flags, replay.sideEffectSuppression, webhooks.durable, interrupt.approverRouting, sandbox.supported and seam-only compensation evidence all become obligations of the surface that needs them; a relaxation is an operator setting with a durability class recorded in bundle v3, never a discovery field; replay suppression is witnessed by a host-declared, machine-checkable effect-seam manifest (RFC 0140 G7's "not enumerable in the spec" answered by making the host enumerate it); compensation and Layer-2 effect identity are core obligations with declared witnesses or move to ext/ at the cut; RFC 0035 is superseded by the packs obligation at the cut; the replay threat model gets §6–§8. C.8 demotes gRPC to ext/ (no client, no advertiser, a proto that calls itself abridged), deletes supportedTransports, removes the legacy A2A/MCP profiles from the v2 tree (adopter inventories closed as measured: one dual-era host, ours), makes negotiation authenticated with an advertised floor, a 90-day refresh SLA and a content-free negotiation.decided event, caps MRTR rounds, decides RFC 0100 UQ4, and writes SECURITY/threat-model-interop.md — the file two Stable documents cite and that does not exist. Register hygiene: RFC 0152/0153 gain the G7–G9 rows that lived only in their spec docs; RFC 0151 G4 reconciled with compensation.md G8; extensions.json's compensation advertiser note corrected; grpc-transport.md:202's "RFC 0094 (in flight)" corrected. 17 migration rows; two proposed register rows; the discovery codemod extended. Adversarial reviews recorded. Windows waived. Accepted 158 · Active 13 · Draft 1.
  • RFC 0170 + RFC 0171 Draft → Active — v2 children C.3 (identity) and C.4 (wire envelope); v2 charter Phase 2, PR C. C.3: the RFC 0165 Subject becomes the required owner record and owner.principal goes; lanes gain session and anonymous (RFC 0165 G6 decided); every lane binds a named trust root through RFC 0154's pipeline with a revocation rule (the corpus had no CRL/OCSP text at all) and an advertised minimum assurance; SubjectLink gets a schema; one grammar per id kind (305 of 329 id/ref sites carry none today) and one handle grammar with a resolvability scope; Idempotency-Key gets a grammar and a 128-bit floor; interrupt tokens carry ow2.<alg>.<kid> (the v1 rotation rule at interrupt.md:397 references a discriminator the format at :389 does not contain); RFC 0164 G4 decided (no profile fold); RFC 0159 G3 confirmed closed. C.4: closed event type with a positive vendor pattern and the 20 codemap decisions recorded as v2Override (0 review rows remain; runOrchestratororchestrator, four core. types fold, artifact events keep noun names); every payload definition closed; the closed-enum growth rule; errors.json; OpenWOP- for every header; closed nested configurable; one events channel; afterSequence with omission meaning from-the-start (RFC 0165 G7 decided; the first event stays 0) and the poll response shape, which disagreed between version-negotiation.md and OpenAPI with no register row, unified. Three codemods (event-type-codemap driven by event-codemap.json, debug-bundle-seq, configurable-v2); 26 new migration rows; three proposed register rows. Adversarial reviews recorded (eight and nine findings, with six charter number corrections). Windows waived. Accepted 158 · Active 11 · Draft 1.
  • RFC 0174 + RFC 0178 Draft → Active — v2 children C.7 (governance) and C.11 (assurance registers and deprecation machinery); v2 charter Phase 2, PR B′. C.7 lands its v1.x half now: RFC 0001 §3 gains supersession/rejection/numbering rules and the Accepted-predicate pointer; GOVERNANCE records the approval-count waiver; the 27 register pairs that lived in RFCS/ root move under registers/; 45 gap rows whose prose said CLOSED while their token carried themselves are re-tokened closed (RFC 0106 G7 among them) and the remaining 343 self-carries become a ratchet; four document banners graduate (agent-runtime, portability, self-hosted-runner — which had forbidden what RFC 0122 permits — and frontend-plugin-packs' RFC status word); two stale gap rows close; RFC 0155's repealed-budget residue closes; new gates check-rfc-status-coherence.mjs and check-waiver-authority.mjs (authority, not only presence — ten pre-0174 high-risk waivers with no named §A.6 override are reported as historic, not passed). C.11 lands check-falsifiability.mjs (the RFC 0167 family's tables parse with closed verdicts) and check-gap-contradictions.mjs (first catch: artifact-type-packs.md said a test did not exist that does), every deprecation kind now needs a detector, schemas/README.md's five stale RFC-status words are corrected and checked, api/redocly.yaml cites the right gate step, and two proposed rows enter the register (gap-local-alias, manifest-deprecated-field — the pack-manifest deprecated wire field collides with the JSON Schema annotation keyword). Windows waived. Accepted 158 · Active 9 · Draft 1. Suite 1.160.0 → 1.161.0 (api/redocly.yaml and schemas/README.md are packed content; no scenario change).
  • RFC 0169 + RFC 0172 Draft → Active — v2 children C.2 (discovery and capabilities) and C.5 (versioning and release); v2 charter Phase 2, PR B. C.2: one capability record type on a closed root, one declaration file replacing the four unschema'd registries, derived profiles with two-axis maturity and an externally-gated disposition; ten migration rows and the openwop.codemod.discovery-document-v2 codemod (refuses rather than guesses on a disagreeing mirror, a bare supported with no versions, or an extension family with no vendor). C.5: major negotiation by protocolVersions[] + root preferredVersion + OpenWOP-Version on a bare origin with unversioned v2 path keys (no /v2/ path space), all 18 axes dispositioned, engineVersion unified with openwop.codemod.engine-version-unify, schemas/v2/ under /spec/v2/, one release identity; nine migration rows; the /v1/ path prefix enters the deprecation register as proposed (27 rows, 29 detectors). Both carry a recorded adversarial review (eleven findings absorbed, including that rest-endpoints.md bullet 3's 400 on unversioned roots inverts in v2 and that MyndHyve's undeclared root profiles[] fails the closed root). RFCS/README's RFC 0167 row had four cells in a three-column table; fixed. Windows waived under GOVERNANCE §"Sole-steward operation". Accepted 158 · Active 7 · Draft 1.
  • RFC 0167 Draft → Active — OpenWOP v2, the program RFC (v2 charter Phase 2, PR A). The umbrella for the v2 major: six axioms; eleven children (0168–0178 ↔ charter C.1–C.11) with a sequencing and a per-child contract (migration table as data, persisted-data disposition, falsifiability rows, a recorded adversarial review); 18 version axes and 28 alias detectors enumerated with dispositions; the per-consumer migration plan for the nine repositories; the Phase 2 exit and v2.0 cut gates. Machinery landed with it, all outside the wire: spec/v1/migrations.json + schema (27 rows; scripts/check-migrations.mjs checks each child's table against it), spec/v1/alias-detectors.json + scripts/check-alias-coverage.mjs (every alias has a register row, machine-true), a codemod contract with a three-part negative control and a self-sabotaging runner (codemods/, scripts/check-codemods.mjs; first codemod openwop.codemod.capabilities-wrapper-removal), 11 new deprecations.json rows (26 total: since, the X- request-header family, replay.fork, debug-bundle seq, credentialProvider, publicKeyRef, the legacy interrupt payloads, a2a-0.3-legacy and mcp-2025-06-18-legacy with their 2027 sunsets, the SDK-only signature header, the RFC 0144 extension-class families). RFC 0001 §3 gains program RFCs and children. Two editorial corrections: the pre-rebrand wop-agents-* example strings, and debug-bundle.schema.json's bundleVersion pattern, which rejected its own documented value "1" (Class-3 widening). Window waived under GOVERNANCE §"Sole-steward operation"; RFC 0147 §A.6 overridden and named. Accepted 158 · Active 5 · Draft 1. Suite 1.159.0 → 1.160.0 (the two schema corrections are packed content; no scenario change).
  • RFC 0165 Active → Accepted — both host legs landed. openwop-app #3631 (2578e025c, ADR 0625) and MyndHyve myndhyve/myndhyve#238 (8a972d193) advertise protocolVersions, persist owner.subject on new runs and synthesize the urn:openwop:legacy subject on old ones, copy tenant + subject onto forks, dual-emit the OpenWOP-* webhook headers and serve a standard ETag / 304 on discovery; the openwop-sdks webhook-helper defect shipped in TS 1.9.0 / Py 1.7.0 / Go v1.6.0. MyndHyve's leg also narrows auth.profiles to openwop-auth-saml — the RFC 0164 erratum remediation — and INTEROP-MATRIX.md's RFC 0163/0164 MyndHyve row is corrected accordingly. Tier-1 + tier-2, both steward-owned. Accepted 158 · Active 4 · Draft 1.
  • Suite 1.158.0 → 1.159.0 — owner-subject-echo reads the first event without a cursor. lastSequence=N means "I have seen N" and the first event is sequence 0, so lastSequence=0 asks a conforming host to skip run.started — MyndHyve numbers from 0 and would have failed the §B.2 echo leg for being right. The leg now omits the cursor. The underlying spec gap (no cursor value for the empty prefix; omission undefined) is RFC 0165 G7, carried to the v2 C.4 child. Found on the openwop-app leg, which had the OPPOSITE defect (a +1 that dropped run.started even with a 1-origin log — fixed there in ADR 0625).
  • G17 has a witness: the unconditional Layer-2 atomic-claim MUST is now drivable, and the assertion that matters is not the one you would write first. idempotency.md §"Concurrent duplicates (Layer 2)" requires the persist guarding a side effect to be an atomic claim and says it MUST hold within a single-instance deployment — and it had no invariant, no scenario, and nothing that could drive it, because witnessing it needs two executors of one run concurrently reaching one chokepoint. New host-sample-test-seams.md §25 (POST /v1/host/sample/test/idempotency/concurrent-claim) + idempotency-concurrent-claim.test.ts; layer2-invocation-claim-atomic moves reference-impl → protocol tier with a real test. **The seam reports the logicalInvocationId each executor minted and the scenario asserts they are identical before asserting delivered === 1 — without that, a host passes by minting different identities and never colliding: one effect because nothing raced, a vacuous pass wearing a green check, and exactly the condition §"Across a recovery boundary" describes. Deliberately not profile-gated and therefore not opt-out-able: the obligation is unconditional, so no host may declare it inapplicable, and flipping an unwitnessed MUST into pass-by-opt-out is what that list must never be used for — an unmounted seam records blocked (applies, unwitnessed, not certifiable), which keeps the reference-host soak green without anyone claiming the requirement was met. The race was believed unreproducible in-process, on the strength of a true statement:** a tier-1 host's emitter noted that the shared ordinal counter hands a second emit a different identity — true of two sequential emits, and it stood for weeks as a reason not to try. Identity minting runs synchronously before the first await, so two un-awaited calls mint the same ordinal, which is what a real re-dispatch does since each executor re-executes the node from the start. That host then measured 2 effects delivered without the claim, 1 with it, and found its own invocation log used INSERT OR REPLACE — the precise inverse of a claim, since it always wins and never reports a conflict. Suite 1.157.0 → 1.158.0; also syncs conformance/package-lock.json, which had drifted to 1.156.0 against a 1.157.0 manifest.
  • Two stale guards reddened the soak, one of them by going off on a refactor that improved the thing it protected. risk-disposition.test.ts asserted that generate-assurance-status.mjs contains const negated = — the negation check that stops a row reading "cannot be closed" as closed. RFC 0166 §A (#1174) replaced that inline regex with the shared register parser, dispositioning by the token at the head of the Status cell, which cannot be fooled by prose at all; the free-text negation logic moved intact into registers-lib.mjs. Nothing regressed — the test was guarding the implementation, not the guarantee, and a strictly better mechanism read as a failure. Rewritten to assert the property in whichever file holds it: the generator dispositions via parseRegister/row.token, the legacy free-text path keeps const negated and its closedMark && !negated requirement, and the bare-substring form must not return in either file. Sabotage-verified — removing the negation guard from the parser still reds it. Second failure: spec/v1/auth.md §"The Subject record" gained a subject.schema.json instance with no <!-- normative-example: --> marker, so RFC 0149 §D's non-vacuity floor correctly reported an undeclared normative example; the marker must sit immediately above the fence with no blank line, which is what a first attempt got wrong. Soak back to green.
  • RFC 0156 §B named four review outcomes that had nowhere to be recorded, and a count that could not tell them apart. §B requires retrospective cross-organization review of waived RFCs, with outcomes ratified|corrective-rfc-required|provisional|withdrawn and an explicit "silence MUST NOT mean ratified". Neither half was implemented: the outcome vocabulary appeared nowhere in the tree, so a review could have happened and left no trace — indistinguishable from no review — and generate-assurance-status.mjs derived "reviews completed" from a free-text match, /retrospective review (complete|closed|done)/i over the gap registers. That regex measures whether someone wrote a sentence, not whether the obligation was discharged: it would have counted a review whose outcome was withdrawn or corrective-rfc-required exactly like a ratified one, reporting compliance over an open defect. §B names four outcomes precisely so they are not interchangeable. New docs/WAIVER-RETROSPECTIVE-REGISTER.md (45 rows, all not-reviewed) + scripts/check-waiver-retrospective.mjs, wired into openwop:check: rows are derived and held in agreement with the tree, outcomes must come from the closed vocabulary, only ratified discharges, and a non-not-reviewed outcome must name a reviewer org — an unattributed ratified is the exact silence-means-ratified substitution §B's last clause forbids, and all four controls were verified by sabotage. not-reviewed is added as a fifth token §B does not define, because the absence of a review needs a name or it gets read as one of the four. The count stays 0 of 45 and that is the correct number, not a backlog: §B review is cross-organization, MAINTAINERS.md lists one maintainer, and the non-steward tripwire has not fired — so a steward self-review recorded as ratified would be the very substitution being guarded against. What changes is that the gap is now per-RFC and recordable rather than a single aggregate, and visibly a blocked obligation rather than an unstarted chore.
  • Suite 1.156.0 → 1.157.0 — owner-subject-echo no longer races the host's asynchronous start. The §B.2 echo leg read the event log immediately after POST /v1/runs and failed a conforming host whose executor appends run.started on the next tick (found on the openwop-app RFC 0165 leg: run.started MUST be present with the run still pending). The leg now waits for the run to leave pending before asserting the echo. A suite defect, not a host one; no wire change.
  • RFC 0166 Active → Accepted — the witness backfill has been reviewed, not just stamped. A process RFC graduates on its corpus gates (all green in openwop:check), plus the §C.2 honesty clause: 25 SECURITY/invariants.yaml entries and 6 spec/v1/extensions.json records were re-judged against what their cited scenarios can observe; 10 moved witnessable-gated → seam-gated (the five anon-actor- invariants and openwop-anonymous-actor probe only /v1/host/sample/anon-surface/; openwop-workload-identity-v1, openwop-a2a-1-0, openwop-mcp-2026-07-28, openwop-compensation-v1 need the §20/§21 seams and record blocked without them), 21 confirmed. docs/witness-baseline.json ratchets lowered to unreviewed 166 / 67 (unwitnessable stays 20). Window waived per GOVERNANCE §"Sole-steward operation". Accepted 157 · Active 5 · Draft 1.
  • Erratum — RFC 0164's empirical premise was false. The RFC and its Phase 0 classification argument said no production host advertised both openwop-auth-saml and openwop-auth-scim; the RFC 0165 host-leg discovery found MyndHyve's live /.well-known/openwop advertising both with no subjectLinking (its RFC 0050 advertisement), so it has failed the RFC 0164 conditional since suite 1.151.0. The host is steward-owned (tier-2), the counterfactual safety-fix classification recorded in RFC 0164 §Compatibility is the one that applied, and the remediation is the RFC 0165 MyndHyve leg dropping openwop-auth-scim (the host cannot link: no <saml:Issuer> parse, no SCIM trust root). RFC 0164 §Motivation, §Implementation notes and register G1 corrected; the v2 charter's §0 premise and memory updated.
  • v2 charter Phase 1, tranche 5 — the event codemap as data and the cross-repo evidence manifest (suite 1.155.0 → 1.156.0). spec/v1/event-codemap.json (schema event-codemap.schema.json, scripts/generate-event-codemap.mjs --check) carries one row per RunEventType — 117 — with its payload $def and a MECHANICAL proposed v2 name under the charter's domain.verb-ed rule (18 rows flagged review: true for a C.4 decision; nothing consumes the file in v1.x, which is the point: the v2 rename edits data every host backfills from). evidence/cross-repo-manifests.json (scripts/generate-cross-repo-evidence.mjs --check) records what every <repo>:<path> evidence pointer in SECURITY/invariants.yaml and conformance/coverage.md resolved to in the sibling checkouts — 24 pointers across 4 repos, commit-pinned and digested — so "verified elsewhere" is a committed record rather than prose. Its first run found two dead pointers (a2ui-surface-no-code-exec, a2ui-surface-no-network-egress cited test files that never existed at that path); both now point at openwop-app's a2ui-render-invariants.test.tsx, which carries both probes by name. New corpus-coherence scenario event-codemap-complete.test.ts (not packed).
  • RFC 0166 Draft → Active — register dispositions, terminal states, witness classes (v2 charter Phase 1, tranche 4; no suite change). Every one of the 919 gap and risk register rows now begins with a closed disposition token (open|closed|transferred:|carried:|externally-gated: for gaps; open|mitigated|accepted|closed|transferred: for risks), written once by scripts/backfill-registers.mjs (367 by the README rule that an unmarked row on an Accepted RFC is the named carried-forward gap) and gated by scripts/check-registers.mjs (no terminal-status RFC with an open gap row; open risks ratcheted). 100 RFCs that graduated without a register get a one-row register that says so. spec/v1/gaps.json (538 gaps, ids openwop.gap.<rfc>.<n>, schema gaps.schema.json, generate-gaps.mjs --check) is the one gap namespace. witness (closed enum) is on all 191 invariants and 73 extension records — classified mechanically and stamped witnessReview: initial-mechanical-2026-09-02, with three ratchets in docs/witness-baseline.json (check-witness-classes.mjs); 19 tests: [] invariants gain a rationale. generate-assurance-status.mjs reads risks through the shared parser: 381 rows (was 140 of 204 visible), 99 open Critical/High (was 72 — the 27 were always there). RFC 0001 §3 gains Rejected; docs/RETROSPECTIVE-QUEUE.md publishes the RFC 0147 high-risk cohort (0148, 0150, 0152, 0153, 0154) as provisional; RFC 0155 §D's uncalibrated 12/4 budget is repealed in favour of the witness requirement. protocolVersions classified as a metadata field in the extension-coverage generator (RFC 0165 leftover). Window waived per GOVERNANCE §"Sole-steward operation".
  • RFC 0165 Draft → Active — v2 preparation: additive wire shapes (suite 1.154.0 → 1.155.0). Three shapes land optionally in v1.x so the v2 cut deletes rather than invents (charter C.2–C.5): (1) root protocolVersions: string[] beside the scalar, strict MAJOR.MINOR grammar, MUST contain protocolVersion, not consulted by profile derivation; the split engineVersion type is recorded, not changed. (2) owner.subject — new schemas/subject.schema.json {issuer, subjectId, tenant, lane, kind, keyClass?, actor?} as a declared optional member of both closed owner objects (the run.started echo also gains the principalKind it lacked), with the consistency rules, the legacy subject (issuer: "urn:openwop:legacy", never linkable), fork copy of tenant + subject (replay.md §"Fork ownership" — the first text to bind fork ownership), a bounded actor chain, and the A2A anonymous-actor rule (RFC 0132 G5 closed). (3) Hosts SHOULD dual-emit the OpenWOP- webhook header family and a standard ETag on discovery; X-openwop- and Capabilities-Etag enter deprecations.json as deprecated with this RFC as authority. Two invariants (subject-record-opaque, subject-legacy-not-linkable; 189 → 191), threat model §4.7. Window waived per GOVERNANCE §"Sole-steward operation"; RFC 0147 §A.6 overridden and named. Active → Accepted gates on the openwop-app and MyndHyve host legs and the openwop-sdks webhook-helper fix.
  • v2 charter Phase 1, tranche 2 — the conformance tarball carries only the suite, and its vendored contract is digest-checked (suite 1.153.0 → 1.154.0). The 28 corpus-coherence scenarios and every src/lib self-test are excluded from the package (conformance/spec-coherence-scenarios.json lists them; the pack-contents gate fails if they return); schemas/CORPUS-STAMP.json now carries a SHA-256 per vendored file and the suite refuses to run in the published layout when any digest mismatches; --offline is declared as exactly fixtures-valid.test.ts. Layout-gate floors re-measured at 1,350 tests / 300 files.
  • v2 charter Phase 1, tranche 1 — per-it requirement rows (suite 1.152.0 → 1.153.0). The conformance ledger records one RFC 0148 §A disposition per test (openwop.it.<file>.<title-slug>) beside the file-level row, --certify emits them, and a generated conformance/requirements.json maps every id to its citations with an alias file gated in openwop:check. This is the durable fix for conformance-certification.md gap G8 and closes the per-assertion half of RFC 0148 G3: the ids derive from test titles (1,927 of 1,950 stable), not from citation text (190 of 1,756 interpolated), which is what made the registry generatable.
  • v2 charter Phase 0 — governance and hygiene for the v2 program (suite 1.151.0 → 1.152.0). (1) COMPATIBILITY.md §5: the v1 deprecation date is now the host-inventory rule (every INTEROP-MATRIX.md host has a non-vacuous v2 bundle, plus 90 days; an 18-month calendar floor applies only if an independent-organization host is in the matrix) instead of "typically 18–24 months". (2) GOVERNANCE.md gains §"Sole-steward operation": one maintainer, two steward-owned hosts, windows may be waived and are recorded, evidence gates are never waived, adopter-facing machinery is built anyway. (3) MAINTAINERS.md §"Major bump to v2.x" rewritten: it pointed at site/src/build.mjs, which moved to openwop-site in June, and covered only the site; it now sequences all nine repositories. (4) spec/v1/deprecations.json — the deprecation register COMPATIBILITY.md §7 now points at: nine RFC- or annotation-backed deprecations (capabilities wrapper, host.* dotted mirror, openwop-core, channel prefix conventions, bare supported: true in A2A and MCP, config.outputArtifactType, config.chatCard, capabilities.auth.subjectLinking, bundle v1) with authority, replacement and removeIn, plus four status: "proposed" rows the v2 charter proposes but no RFC has made (Capabilities-Etag, contractProvenance, the un-prefixed webhook signature form, the 409/410 interrupt-token overlap). Schema at spec/v1/deprecations.schema.json; scripts/check-deprecations.mjs (unique ids, schema-valid, every cited source file still contains the surface token, removeIn is a v2 version) runs in npm run openwop:check. The register is an index in v1.x; it binds at v2. (5) Three "Open spec gaps" rows said "deferred" for endpoints that are in api/openapi.yaml (agent-roster.md, agent-org-chart.md, agent-evaluation.md) — closed; RFC 0106's register row G7 said "the sole open gap — the Active → Accepted gate" on an Accepted RFC — closed. (6) --certify defaults to bundle v2 and the bundle v1 schema is marked deprecated (see the conformance changelog). (7) The RFC 0050 reference suite moved out of its scenario file (RFC 0163 gap G5); RFC 0050 gains a register with that row closed.
  • RFC 0164 Active → Accepted — tier-1 witness on the reference host. openwop-app #3620 (873efc466, ADR 0623) advertises both openwop-auth-saml and openwop-auth-scim only when its SAML/SCIM realms are aligned and a SCIM trust-root seat is configured, and otherwise narrows the advertisement to a single profile instead of shipping the both-profiles-no-flag shape; both advertised ⇒ subjectLinking: true + subjectLinkKey: "opaque-idp" from one gate; an unbound record in a combined deployment is refused on the SAML lane (the RFC 0163 unbound carve-out survives only for single-profile deployments — the tightening RFC 0164 §A.2 asked for). auth-subject-link-alignment.test.ts (5 legs: misaligned realms + seat ⇒ openwop-auth-scim dropped; aligned + no seat ⇒ dropped; aligned + seat ⇒ both profiles + subjectLinking: true + subjectLinkKey; aligned + seat + unbound ⇒ SAML 401 subject_link_unbound; single-profile + unbound ⇒ RFC 0159 deny-only survives) plus saml_assertion_unbound_refused in auth-subject-link.test.ts; two sabotage reverts confirmed load-bearing (revert the SCIM drop ⇒ alignment leg reds 1/4; revert the unbound refusal ⇒ fail-closed leg reds 1/14); tsc --noEmit clean, auth suites 25/25, npm run ci green (5034 tests, 0 failed); the witness was re-verified by the adopting session after the implementing agent stalled, not inherited Single-witness under the bootstrap waiver; the G1 sweep at Accepted still finds no production host advertising both profiles. Accepted 156 · Active 4 · Draft 1.
  • tenantId on POST /v1/webhooks and RFC 0048's owner.tenant were the same word for two different scopes, and the suite conflated them. webhooks.md §Register has always documented the field as "Workspace under which the subscription lives. Caller MUST be a member.", and every example in that document passes a workspace (workspace-123, workspace-prod). RFC 0048 §A defines tenant as something else — the top-level isolation boundary — with workspace an optional sub-tenant beneath it. On the common host where tenant ≡ workspace the two coincide, which is why discoverOwnedTenant() reading owner.tenant worked everywhere it had been run. Reported 2026-09-02 by a tier-2 host with a real workspace layer, whose schema-correct owner is {tenant: "<instance>", workspace: "<workspaceId>", …}: the suite derived the instance label — a scope nobody is a member of — and registration 403'd by design. The host was conformant; the suite was wrong, and its own docblock already stated the right intent ("a tenant the calling bearer provably OWNS") while reading the field that does not carry it. Suite 1.149.0 → 1.150.0 prefers owner.workspace. webhooks.md v1.1 → v1.2 states which RFC 0048 claim the field carries, that the name predates RFC 0048 and is retained for wire compatibility (renaming a required request field is a §2.2 break), and — the part worth being explicit about — that a host MUST NOT accept its tenant label as an alias here, since that would mint membership semantics for a label that has none in order to satisfy a caller's misreading. Clarifying and additive: it states the membership scope the route already required.
  • RFC 0163 Active → Accepted — tier-1 witness on the reference host. openwop-app #3614 (2f6ca969d, ADR 0620) emits capabilities.auth.subjectLinkKey: "opaque-idp" from the same gate as subjectLinking, reconstructs the fixture's signed <saml:Issuer> in its validator and surfaces it on the principal, binds the SCIM connection to an IdP entityID (idpUrl accepted on the provision seam), and refuses to consult the link when the assertion's issuer does not equal that entityID — on the validate seam and the production ACS. In-process witnesses on the reference host: test/auth-subject-link.test.ts (two synthetic IdPs with distinct signed issuers — advertisement subjectLinkKey === 'opaque-idp'; same-root provision + assertion authenticates as the positive control; cross-root collision refused 401 subject_link_trust_root_mismatch; the RFC 0159 legs — SCIM-deactivate ⇒ SAML denied, mutable-key hygiene — stay green), test/auth-saml-sso-trust-root.test.ts (production ACS: same-root mints a session, cross-root refuses with no cookie) and test/auth-saml.test.ts (signed <saml:Issuer> in the canonical + principal.issuer surfaced). npm run ci: backend 470 files / 3013 tests, 0 failed. The SSRF widening was sabotage-verified: with the trust-root refusal reverted the cross-root leg returns 200 authenticated:true and the assertion fails, so the refusal — not the 403 — is load-bearing. Single-witness under the bootstrap waiver (as RFC 0159); no tier-2/3 host advertises subjectLinking. INTEROP-MATRIX.md gains the subject-linking sub-table; coverage.md behaviour grade → host-pass. Accepted 155 · Active 4 · Draft 1.
  • RFC 0163 Draft → Active — the subject-linking hardening locks its wire shape after three pre-lock amendments. (1) capabilities.auth.subjectLinkKey names classes, not vendor attributes: {opaque-idp, configured-immutable} replaces the Draft's {opaque-idp, oid, immutable-id} — the two Entra names carried no witness the suite could distinguish and were an allow-list already anticipating its fourth member. (2) The subjectLinking:true ⇒ subjectLinkKey co-requirement is a schema if/then as well as a prose MUST; the prose-only Draft left a linking claim with no key schema-valid (its own R5). It binds only a flag no production host emits — MyndHyve's live discovery has no auth block, openwop-app advertises the profiles only behind seams — so it is a tightening of an unemitted shape, not a §2.1 closure. (3) §B.1 pins the trust-root representation: the SAML assertion's signed <saml:Issuer> entityID MUST equal the IdP entityID bound to the SCIM connection at configuration time, never inferred from a request; "correspond" without a representation was unwitnessable. Three invariants registered (subject-link-leaver-deny, subject-link-key-class-declared, subject-link-same-trust-root) — RFC 0159 had a threat-model row and no invariant. Fixture self-tests moved out of the scenario file (certification gap G8). The 7-day window is waived under the single-maintainer bootstrap rule, as for RFC 0159 and 0050, and recorded in MAINTAINERS.md. Active → Accepted gates on the reference host emitting subjectLinkKey and enforcing the same-IdP check. Suite 1.147.0 → 1.148.0.
  • A load-bearing citation pointed at a section that does not exist. RFC 0159 (four times), RFC 0163 and the Stable auth-profiles.md §Subject linking all cited "RFC 0048 §D owner-echo determinism" as the reason link-not-merge is required. RFC 0048 §D is titled "Cross-workspace isolation" and contains only the run_forbidden MUST; the owner echo is §C and the fork determinism is RFC 0006. The design is right; its stated authority was not. Corrected in all three places. This is the "armoured wrong citation" shape docs/EVIDENCE-DISCIPLINE.md practice 13 describes: a reader follows it, finds a real section, and stops.
  • An unconditional MUST with no witness of any kind, found by a host reading its own code. spec/v1/idempotency.md §"Concurrent duplicates (Layer 2)" requires the persist guarding a side effect to be an atomic claim — compare-and-set / insert-if-absent that at most one executor can win — and says so for single-instance deployments explicitly. A tier-1 host reported 2026-09-01 that its invocation log used INSERT OR REPLACE: the precise inverse of a claim, since it always wins and never reports a conflict, so the "and the other observes the hit" half of the rule cannot happen. Nothing in the corpus would have caught it. New invariant layer2-invocation-claim-atomic (185 total) plus SECURITY/threat-model-compensation.md §4.5, which is where it belongs because the inverse of a duplicated effect is a duplicated refund — compensation faithfully reverses a history that was already wrong, and no compensation-side check can recover from that. Registered reference-impl tier with an empty tests list, deliberately: driving it needs two executors of one run concurrently reaching one seam, which the black-box suite cannot cause without a host seam, and a probe asserting it would pass either way (RFC 0148 §A). The measurement behind the gap: RFC 0158's durability/duplicate-delivery row would cover it, but 5 of that RFC's 6 conformance rows have no scenario at all (only poison-exhaustion does), and duplicate-delivery is named in no rung's required evidence — durable-single-instance requires only kill-after-accept and kill-during-execution — so a host can hold that rung honestly while violating this MUST. Recorded as gap G17. Also documents the third failure mode neither the clause nor the host had named: a lease is liveness-dependent and fences only while the loser is healthy enough to notice, which is the case that does not need fencing.
  • A reader with an intra-region duplicate-effect hazard arrives at the section that does not bind them. RFC 0150 is titled "…and split-brain safety" and its §D is "Fenced multi-region effect ownership", so a host reasoning about two executors of one run inside one region lands there — and every obligation in §D above the separation principle is conditioned on claiming multi-region effect safety, which such a host does not claim. The rule that does bind them is §B: the durable invocation-log claim MUST be an atomic compare-and-set / insert-if-absent, which idempotency.md §"Concurrent duplicates (Layer 2)" states MUST hold within a single-instance deployment. Both already said so; nothing pointed from one to the other. Found when the openwop-app reference host reported an ADR blocked on §D "landing first" for a hazard §D does not govern — a Cloud Run instance throttled to near-zero CPU letting a dispatch lease lapse while the run is alive, with the sweeper then re-dispatching. Non-normative pointer added to §D naming §B as the intra-region home, noting that the atomic claim is liveness-independent where a lease is not (the losing executor need not be alive or aware — it loses at the moment it attempts the write), and flagging that §D's first two sentences are unconditional. No new obligation; the one MUST in the added text restates §B's. RFCS/0150-effect-identity-replay-and-split-brain-safety.md §D.
  • Correction to the entry below: a never-published suite version is routine here, not a defect. The Pending suite release check was designed on the assumption that a pinned-then-superseded version was an anomaly worth naming — 1.143.0 was cited that way. Its first CI run measured 59 of the last 120 pinned versions in that state. The suite is bumped on most merges and tagged in batches, so gaps in the version line are the release cadence working, not a fault; what actually hurt was only ever the current pin sitting unpublished while npm served something older, which is the case that fails. The skipped-version report is now a count plus the five most recent rather than a 59-line dump — output nobody reads is output that hides the line that matters — and the docblock no longer describes the normal case as a problem.
  • A suite bump that is never tagged was indistinguishable from one about to be — and nothing looked. openwop:check step 9 asks whether a published version still matches the tree, and passes when the version is absent, because that is exactly what a fresh pre-tag bump looks like. Nothing asked the other question: did main's pin ever reach npm at all? It has gone wrong twice measurably — 25 bumps once accumulated (1.73.0 → 1.98.0) while npm served 1.73.0, caught by a person rather than a gate; and 1.143.0 was main's pin between #1152 and #1153, was never published, and was superseded by 1.144.0, so that version number does not exist on npm and never will. New scripts/check-pending-suite-release.mjs + a scheduled Pending suite release workflow separate the two findings by what can be done about them: a pending current pin fails past a 24h grace window (recoverable — tag it), while skipped historical versions are reported and counted but never fatal (unrecoverable — the tree each named is gone, so publishing one now would misrepresent its contents; failing a build over an unfixable fact just teaches people to ignore the check). The grace window exists because a bump landing on main before its tag is the release process working, not failing. Deliberately not in openwop:check: that gates pull requests, and reding a contributor's unrelated PR because a maintainer missed a tag punishes the wrong person. All six paths verified by control, including the two that matter most — an unreachable registry exits 2 under CI rather than passing, and a 16.4h-old unpublished pin fails once the grace drops below its age.
  • The soak's remaining 40 failures were unreachable, not newly broken. With the host no longer dying mid-run (241 failures → 40), the scenarios that had never executed became visible for the first time — 32 further capability-gated profiles with no strict-mode opt-out, and zero failures of any other kind. Verified against the live /.well-known/openwop exactly as the first cohort: 32 of 32 unadvertised, 0 advertised-but-failing. Worth recording that workflowChainPacks.supported and workflowChainPacks.subChains.supported both needed entries despite workflowChainPacks already being listed — opt-out matching is exact-string, not prefix, so every gated sub-key needs naming. .github/workflows/conformance-soak.yml.
  • Conformance Soak had no green run in its last 200 — and the number it reported was not about the suite. The gate showed 241 of 479 test files failing against the SQLite reference host, which reads as a large regression. Every one of those failures was ECONNREFUSED 127.0.0.1:3838: the host process dies partway through the run, and each scenario after that point reports an empty socket rather than anything about the host's conformance. Two independent faults were stacked behind that one number. (1) The host was dying, on a native assertion in better-sqlite3's Statement destructor (RemoveEnvironmentCleanupHook … Assertion failed: (env) != nullptr) — v11 declares no engines.node at all, so the workflow's move to Node 24 installed it silently; 12.2.0 onward declares Node 24 explicitly (fixed in openwop-examples#26, at ^12 not ^13 because v13's >=22 would invalidate the host's own >=20 engine claim). (2) 13 capability-gated profiles the suite has grown had no matching strict-mode opt-out, worth ~40 further failures that would have fired against a perfectly healthy host. Membership was established by booting the host and reading its live /.well-known/openwop rather than by pattern-matching the failure text: all 13 resolve to undefined at the document root, under capabilities., and in profiles[]. dispatch.perItemInput is the instructive one — dispatch is advertised, only the sub-key is not, so the opt-out is scoped to the sub-key and the rest of the dispatch surface stays strictly enforced. An opt-out must mean "the host does not claim this", never "the host claims it and fails", or the gate stops being able to go red for a real regression. .github/workflows/conformance-soak.yml.

Security

  • RFC 0164 — the SCIM ⟷ SAML leaver contract is mandatory for any host advertising both profiles. RFC 0159 landed the combined-deployment leaver contract opt-in behind capabilities.auth.subjectLinking, and RFC 0163 hardened it but kept the gate, so a host could advertise both openwop-auth-saml and openwop-auth-scim, omit one boolean, and ship the leaver bypass while the suite recorded inapplicable. RFC 0164 (Active) makes the obligation follow the profile pair: advertising both implies RFC 0159 §A and RFC 0163 §A/§B; subjectLinking becomes a derived advertisement that MUST be true (schema conditional on profiles[], composed with the RFC 0163 conditional under allOf) and is deprecated toward v2; an unlinkable subject fails closed on the SAML lane, and a host that cannot honour the contract MUST NOT advertise both. Classified additive, deliberately argued: COMPATIBILITY §4's default-on row says safety-fix, but its harm set — hosts that advertised both without the flag — is empty (INTEROP-MATRIX; MyndHyve's live discovery has no auth block; openwop-app advertises only behind seams and already derives the flag), and the counterfactual is recorded in the RFC. No advisory id exists and none is invented. Both subject-link scenarios re-gate on the profile pair and the advertisement leg now FAILS the vulnerable shape; four invariants (subject-link-mandatory-when-both-advertised new); version-negotiation.md gains the combined-host migration section; waiver recorded in MAINTAINERS.md. Active → Accepted gates on the reference host narrowing its advertisement when its lanes' realms are misaligned. Suite 1.150.0 → 1.151.0.
  • RFC 0163 — SCIM ⟷ SAML subject-linking hardening: a declarable, witnessable link-key class + a same-IdP trust-root MUST. RFC 0159 made a SCIM-deactivated leaver fail-close on the linked SAML lane, but two of its safety obligations survived only as negative-existence claims-checks a suite cannot fully witness (§A.2 "MUST NOT key on a mutable/PII attribute", §A.4 "MUST NOT silently fall back") and it left the two lanes' trust-root relationship open (UQ4). RFC 0163 (Draft, additive) hardens both, gated on the same opt-in capabilities.auth.subjectLinking:true: (1) a new optional capabilities.auth.subjectLinkKey — a closed enum of allowed classes only (opaque-idp, oid, immutable-id) — that a subjectLinking:true host MUST advertise and MUST honour, with email/userName/any mutable key made inexpressible by construction (the enum's closedness is the witness that converts RFC 0159's negative-existence prohibition into a positive advertisement); and (2) a normative same-IdP trust-root MUST — the SAML assertion issuer MUST correspond to the SCIM provisioning IdP before a link may form, so a cross-IdP identifier collision cannot join two different principals. Both bind only hosts already advertising subjectLinking:true; no host conforming to RFC 0159 or RFC 0050 today de-conforms. New optional capabilities.auth.subjectLinkKey enum; auth-profiles.md §"Subject linking" gains the key-class declaration + the same-IdP MUST; RFC 0159 gains an Amended by → 0163 pointer; SECURITY/threat-model-auth-profiles.md §4.5 cross-IdP mitigation gains the §B.1 MUST and §6 residual bullet notes the declarable key class strengthens the §A.2 residual. The mandatory-for-combined-deployments tightening (RFC 0159 UQ1) remains a deferred breaking follow-up, explicitly out of scope.
  • Suite 1.145.0 → 1.146.0. Adds the RFC 0163 conformance scenario auth-subject-link-key-class.test.ts (published tarball content changed → identity bump): the §A advertisement leg is executable + server-free (a subjectLinking:true host MUST advertise a subjectLinkKey in the closed enum); the §B same-IdP-trust-root behavioral leg is declared + phased (soft-skips blocked until a two-trust-root fixture is engineered — RFC 0163 gap G-fixture), mirroring how RFC 0159 phased its cross-lane behavioral legs. Capability-gated on auth.subjectLinking.
  • Suite 1.144.0 → 1.145.0. New spec-coherence-registry.test.ts assertions pinning the packaged files list (published tarball content changed → identity bump).
  • The property that makes spec-coherence dispositions comparable across hosts was load-bearing and tested nowhere. A row is inapplicable only when V1_DIR is null, which happens only in the published layout — and resolveLayout() keys off PKG_ROOT, not the consuming repo, so an npm consumer's parent is always node_modules/@openwop/, which never contains schemas/ whatever the host's checkout path. Verified against a real npm install: published, V1_DIR null. npm-consuming hosts agree by construction — and nothing held the artifact to it.
  • Two peers reached that by measurement, from opposite errors, which is why it needed pinning. One generalised "the disposition is a property of where the bundle was cut" — true of their host, whose own runner sets OPENWOP_CONFORMANCE_ROOT on finding a sibling checkout, and false of the artifact. The other read the layout code rather than assume, found the PKG_ROOT keying, and narrowed a warning that would otherwise have told every host their numbers were incomparable — making the disposition useless for exactly the cross-host comparison it exists to enable.
  • The invariant guards a change that would look like an improvement. Adding spec to the package files"ship the corpus so consumers can run those tests too" — would give npm consumers a V1_DIR, the 28 would execute instead of flipping, and every host's bundle would shift with nothing going red. Now a hard failure naming that consequence, plus a companion assertion that schemas/ stays shipped, since that is what selects the published branch at all. Both verified by sabotage.
  • RFC 0159 §A.2 threat-model entry landed — the reference was dangling. RFC 0159 §A.2 cited SECURITY/threat-model-auth-profiles.md for the mutable/PII link-key join-and-inherit vector, but no such entry existed. Added §4.5 "SAML ⟷ SCIM subject linking" (STRIDE: join-and-inherit spoofing, leaver elevation, cross-tenant/-IdP spoofing, each with the §A opaque-key / fail-closed / same-tenant mitigation) + a §6 residual-risk bullet for the unprobed-pair negative-existence limit (UQ2). Additive doc-only; resolves the §A.2 pointer. SECURITY/threat-model-auth-profiles.md.
  • Suite 1.143.0 → 1.144.0. Adds the RFC 0159 conformance scenario auth-subject-link.test.ts (published tarball content changed → identity bump), plus the capabilities.auth.subjectLinking schema property and the auth-profiles.md §Subject linking prose.

Security

  • RFC 0159 — SCIM ⟷ SAML subject linking closes the combined-deployment leaver bypass. RFC 0050 defined the SAML and SCIM profiles as independent lanes, so a person deactivated over SCIM (active:false) could still authenticate over SAML — the two lanes map to structurally different RFC 0048 principals and nothing tied them together. RFC 0159 (Active, additive) adds an opt-in obligation, discoverable via capabilities.auth.subjectLinking: a host that sets the flag MUST link the SAML and SCIM principals of one human on an opaque, IdP-stable id (SCIM externalId ↔ persistent SAML NameID), same-tenant only, and MUST fail-close the linked SAML identity when SCIM deactivates it (composing RFC 0049 §C). The link key MUST NOT be a mutable/PII attribute (email/userName) — a mutable-key join is an account-takeover vector. Link, not merge: durable subject keys stay distinct (a merge would break :fork replay + RFC 0048 §D owner-echo). New optional capabilities.auth.subjectLinking boolean; auth-profiles.md §"Subject linking (SAML ⟷ SCIM)"; new gated conformance scenario auth-subject-link.test.ts. RFC 0050 gains an Amended by → 0159 pointer. Additive (opt-in flag; no host conforming to RFC 0050 today de-conforms); the mandatory-for-combined-deployments tightening is a deferred breaking follow-up (RFC 0159 UQ1).
  • RFC 0064 §F tool-failure honesty — a failed tool was wire-indistinguishable from one that succeeded-empty. agent.toolReturned's failure discriminators (error, outcome, status) already existed but were all optional, so a bare {agentId, toolName, callId} return on failure was schema-legal — making tool-failure honesty a property of graph topology (a downstream node re-checking) rather than of the tool-return contract. New normative §F: a non-success agent.toolReturned MUST carry a discriminator — error populated (_errorObject, and status: 'error' under prePostEvents), or a §C/§D gate status (forbidden/rate_limited) — and a return with outcome/error absent and status ∈ {absent, ok} MUST NOT represent a failure. error.code is free-form, so execution errors, timeouts, and capability-precondition refusals (e.g. host_capability_disabled, durationMs absent) are all nameable without a new enum member. Additive: mandates populating existing fields — no schema, error-code, event-type, or invariant change. Adds tool-hooks-failure-honesty.test.ts + the toolhooks seam's simulateToolError arm. Closes host-program blocker WFAU-4. RFCS/0064-*.md §F, spec/v1/host-sample-test-seams.md.
  • The spec-coherence registry's docblock made the file lie to a reasonable reader. It named the seven host-touching scenarios in prose under "What is deliberately NOT here", so grep -c "<scenario>" spec-coherence.ts returned 1 hit for the excluded ones — reporting them as members of the flip set. A peer checking membership that way was one step from reporting that host-behaviour rows had been downgraded to "does not apply to you" as a credit, which is the dangerous direction. They caught it only because two counts disagreed: 28 members and those 7 included cannot both be true.
  • The exclusions are now an exported Set with the same standing as the inclusions. SPEC_COHERENCE_EXCLUDED replaces the prose list, and two new invariants make it checked rather than asserted: the excluded set must equal the derived host-touching set, and the two sets must be disjoint and jointly exhaustive over the V1_DIR-gated files — so no scenario can be both "does not apply to any host" and "applies but was unwitnessable", and none falls through to the unclassified marker. Both verified by sabotage. A text search still matches both sets, which is inherent to any file that names what it excludes; the docblock now says so, and membership has a programmatic answer instead of only a paragraph.
  • OPENWOP_WEBHOOK_RECEIVER_PORT — the receiver bound an ephemeral port, which made the new tunnel route unusable in practice. OPENWOP_WEBHOOK_RECEIVER_URL fronts the suite's own in-process receiver, and a tunnel has to be aimed at a port the operator knows in advance; the receiver called server.listen(0, …). Found by standing up a real TLS front and trying to use the feature, not by reading the code — the resolver, its unit tests, and the wired control were all correct and the thing still could not be operated. Pinnable now; unset remains ephemeral.
  • Suite 1.140.0 → 1.141.0. The identity gate added two days ago caught this branch changing published suite content without a bump — 3 file(s) differ naming webhook-receiver.ts, its test, and the scenario. First time it has fired on a change that was not mine, which is the case it was actually built for.
  • 28 conformance scenarios were reporting blocked in every host's certification bundle about a host they never examined. They read spec/v1/.md to check the corpus is internally coherent — the protocolVersion grammar, error-envelope shape, normative-example extraction, RFC lifecycle. spec/v1/ is deliberately not bundled in the published tarball, so they describe.skipIf at collection time, no test body runs, no reason is recorded, and RFC 0148 §A resolves an all-skipped file with no reason to blocked. A host operator reads blocked and cannot tell it from a real gap in their own implementation. Reported by a tier-2 host that measured 13 such rows — a third of their undiagnosed set* — and found out what they were only by pointing OPENWOP_CONFORMANCE_ROOT at a spec checkout and watching 85 assertions pass in about a second, 59 of them against a dead localhost:9.
  • The fix is correct use of an existing disposition, not a new one. RFC 0148 §A defines blocked as "advertised behavior could not be exercised…" — and there is no advertised behaviour here; nothing about the host was ever going to be exercised. inapplicable ("the requirement does not apply to the captured discovery/profile set") is the honest label, and it is in CERTIFIABLE, so these rows stop counting against a host that cannot affect them. A sixth disposition value was considered and rejected: certification-bundle-v2.schema.json enumerates five and verifyBundleV2 is a published consumer contract, so adding one is a wire break for every existing verifier.
  • Seven scenarios deliberately keep blocked, and the distinction is the whole answer. replay-side-effect-suppression, data-residency-admission, profile-discovery-core-alias and four others gate on V1_DIR and drive the host. They assert advertised behaviour that a missing dependency prevented exercising — §A's definition of blocked, verbatim. Calling them inapplicable would tell a host that a requirement about its own behaviour does not apply to it, which is a worse error than the one being fixed.
  • The registry is a list, so it gets a guard that re-derives it. Membership is mechanical — gates on V1_DIR and never calls driver — so spec-coherence-registry.test.ts recomputes it from source every run and names the file to add or drop. Without it a new corpus scenario would silently report blocked in every host bundle forever, and one that grew a driver call would silently start telling hosts a real requirement is inapplicable. Neither would surface as a failure anywhere, which is exactly how the original defect survived. Both drift directions verified by sabotage, plus a negative control proving the branch — not the test — is what changes the disposition, and a guard that a failing corpus scenario is never laundered into inapplicable.
  • The README advertised the conformance suite at v1.73.0 — stale by 66 minors, in two places on one line — and nothing checked it. Reported by a downstream consumer syncing openwop.dev, not by any gate here. The irony is the useful part: v1.10.0 was itself the cycle about @openwop/openwop-conformance@1.138.1 on npm not being the 1.138.1 in the tree — "the version had stopped identifying anything" — and the fix compared the tree to the registry while the README went on advertising a third, older number that matched neither. Closing one gap made the adjacent one easier to miss, because the surface felt covered.
  • Two of the other three artifacts on that line were stale too; one was right. @openwop/openwop said v1.6.1 (npm serves 1.8.0, published this cycle), openwop-client said v1.5.0 (PyPI serves 1.6.0), and openwop-sdks/go said v1.5.0 — correct. That last one matters more than it looks: openwop.dev had already shipped "Go reference SDK … Pinned at v1.5.0" publicly, sourced from this line on the stated convention that it is authoritative. It is authoritative; it was simply also wrong three times out of four, and the one public claim riding on it happened to land on the true one.
  • Two new guards, split by what is knowable offline. generate-protocol-status.mjs now asserts the README's advertised conformance version against conformance/package.json — a local fact, always checkable. scripts/check-advertised-versions.mjs (step 8 of openwop:check) checks the three SDK versions against npm, PyPI, and the Go proxy — network-dependent, so it carries the same three-outcome discipline as the identity gate: UNKNOWN is reported as UNKNOWN, tolerated locally, and fatal under CI=true. The split is deliberate — folding a sometimes-runnable check into an always-runnable one forces the combined gate to tolerate UNKNOWN everywhere, which is how a dead gate stays green.
  • The first draft of the offline guard was half-toothless, and its own negative control caught it. It collected every parseable claim and hard-failed only when the total was zero — so with two claim sites, one reformatting out of range left the other matching, the count stayed 1, and the unparseable site passed in silence. Exactly the shape the check exists to prevent. Now checked per site: anchor present + version unparseable is a hard failure. Seven negative controls across both guards, each verified to bite rather than assumed.
  • COMPATIBILITY.md §1 now states the v1 wire-contract freeze date. It said nothing, so a consumer citing "the v1.0 wire contract froze 2026-05-08" had to source it from a CHANGELOG.md release entry — which is why openwop.dev carried that date for months with no citable anchor and a sibling session could not verify it and correctly declined to invent one. Three dates sit close together and name different events: the wire froze 2026-05-08, v1.0.0 first published 2026-05-11, and the close-out release — what the README banner calls "v1.0 core locked" — was 2026-05-12. The freeze is the one this document's additive-only guarantee runs from, and it is now in the file that makes the guarantee.
  • The identity gate added in 1.10.0 would have reddened main on every commit, and a CHANGELOG-only PR is what revealed it. schemas/CORPUS-STAMP.json embeds corpusCommit = git rev-parse HEAD at pack time, so it changes on every commit whether or not a shipped byte moved — making the tree differ from the published tarball permanently until the next suite bump. It now compares the stamp's meaning: suiteVersion still counts (a stamp claiming the wrong suite version is real drift and still fails), corpusCommit is a provenance label rather than contract content. A gate that cries wolf on every commit is a gate someone turns off, which would have cost more than the drift it was built to catch. Both negative controls verified to still bite — a wrong suiteVersion fails, and a one-line scenario edit fails.

1.10.0

2026-08-25

the cycle that asked every green what it had measured

No new RFCs and no Active → Accepted promotions (Accepted 153, Active 4, Draft 1). Conformance suite 1.138.1 → 1.139.0 (465 scenario files). All wire shapes additive per COMPATIBILITY.md §2.1; one Class 3 conformance-affecting clarification, recorded in §3. The through-line: a green result is a claim about what ran, and this cycle went looking for the ones where nothing had. Every defect below was invisible not because it was subtle but because its symptom was indistinguishable from success.

  • @openwop/openwop-conformance@1.138.1 on npm was not the 1.138.1 in this tree. Fifteen files of drift — a new globalSetup, seven scenarios, vitest.config.ts, the package README, the corpus stamp — had landed under one version number since v1.9.0 published it. A host pinning 1.138.1 and a reader of this repo were looking at different suites while both believed they were current. The version had stopped identifying anything. Bumped to 1.139.0 across all three pins; a minor rather than the patch the net-new-scenario rule implies, because global-setup.ts and forkDeclined change what every consumer's run does.
  • Nothing caught it because the publish guard asked whether the version EXISTED, not whether it was THIS oneif npm view "$PKG@$VER"; then skip; fi, a version-number comparison standing in for a content comparison. Its only symptom was a publish step printing "already on npm" and exiting 0, which is exactly what a correct no-op prints; and since that step is skip-if-already-published, tagging a release in this state would have gone green and shipped nothing. New openwop:check step 8 (check-published-suite-identity.mjs) packs the tree, fetches the published tarball for the same version, and compares per-file SHA-256. Three outcomes and none of them is silence: OK, FAIL naming every file, and UNKNOWN when the registry is unreachable — reported as UNKNOWN rather than folded into a pass, because "could not look" and "looked and it matched" are different claims. The publish step now verifies identity before it skips.
  • That gate then shipped dead on its first CI run, and its own UNKNOWN state is what said so. A --fetch-retry-maxtimeout below npm's default mintimeout is accepted by npm 11.6 and rejected outright by the npm in CI, so every CI run reported UNKNOWN and exited 0. Had UNKNOWN been folded into OK — the obvious simplification, and precisely the collapse the original publish guard made — it would have shipped as a permanently-dead gate reporting success. Fixed by owning the wall clock instead of delegating it to registry config, and by making UNKNOWN a failure whenever CI=true: an environment with a registry that cannot reach one has a broken check, not an absent network.
  • The suite computed an honest RFC 0148 §A disposition on every run and published it on one. Every scenario file already recorded blocked/skipped/inapplicable with a reason; OPENWOP_LEDGER_PATH's only writer was cli.ts inside --certify, so a plain vitest run computed the disposition and discarded it at process exit, leaving N passed as the sole artifact. Two host implementers independently read that line as coverage. A globalSetup now prints a run-end summary of what did not witness; it deliberately does not touch the exit code, because "did any assertion fail" and "what did the run witness" are different questions and conflating them is what produced the gap.
  • Its own first draft reproduced the defect it exposes — the reader keyed on entry.id, a field the ledger has never written, and the (unnamed) fallback made a wrong reader look like missing data. Fixed by importing the real LedgerEntry type so the compiler is the oracle.
  • All three reference hosts had been emitting invented webhook delivery headers for sixteen days (X-openwop-Subscription-Id, X-openwop-Signature-Timestamp, bare-hex signature, no X-openwop-Event-Type) — the exact names the suite removed on 2026-08-09 when a tier-2 host emitted what webhooks.md shows and failed. Suite and hosts were mirrors of each other; the spec was the oracle neither was checked against. The suite got fixed; no host did. Signed bytes were always correct, which is why nothing about security changed and why nothing detected it. Fixed and re-measured in openwop-examples#22/#23/#24, red-before-green by sabotage on both lineages.
  • Four mechanisms kept that one green, and each hid it differently: pre-2026-08-09 suites asserted the names the hosts emitted; --certify on a default-env boot recorded the scenario blocked because the SSRF guard refuses the loopback receiver; the one bundle run that did execute the assertions went executed-fail and was filed "not diagnosed here"; and CI ran it red on main every push while reporting success, the rate sitting ten points clear of an 85% pass-rate floor. A pass-rate floor converts a specific, named, reproducible failure into an anonymous number. postgres-host-conformance now names its failing scenarios before reducing them to a fraction — and that block itself shipped broken, printing nothing while a scenario was failing, because vitest colourises the marker glyph. It now strips ANSI and self-checks: if the summary reports N failures and it names zero, it says the matcher is broken rather than the run clean.
  • OPENWOP_WEBHOOK_ALLOW_PRIVATE opens three gates and the contract named one. The loopback receiver is plain http://127.0.0.1, which webhooks.md forbids three separate times — scheme, registration-time address, delivery-time re-resolution. Gates 2 and 3 are independent MUSTs at different layers, so "which layer must the opt-in reach" is forced, not a matter of taste: a relaxation reaching one layer cannot witness at all. A tier-2 host validating scheme-before-address was blocked by a gate the contract never named and reported the scenario permanently unwitnessable — correctly. Such a host is not non-conformant; the scenario sits in no profile floor.
  • SECURITY/threat-model-secret-leakage.md §4.9 — the two layers are not symmetric. §4.8 scopes a relaxation flag across surfaces; §4.9 covers the two layers of one surface. A delivery-time relaxation is per-delivery and leaves nothing behind; a registration-time one writes a durable subscription row pointing at a private address that survives the flag being turned off, and §"Delivery semantics" lets operators re-activate by re-registering — a maintenance path that never re-crosses the check. SHOULD + code review per RFC 0144; no new invariant, since both layers are already MUSTs.
  • a2a-push-egress-ssrf required half a guard, and the test could not tell which half ran. RFC 0100 §4, a2a-integration.md §D.6, the invariant note and RFC 0100's own worked example all required the push URL to "pass the RFC 0093 webhook-egress SSRF guard" and then abbreviated that guard, in every one of its four statements, to its address arm — so a host refusing http://10.0.0.5/push while accepting http://push.example.com/ satisfied every written word and shipped a plaintext push channel. Classified W3C Process Class 3 in COMPATIBILITY.md §3 on the RFC 0057 §D precedent: no MUST moved and nothing conforming stops conforming, because the guard always contained that arm.
  • Its conformance leg was worse than its prose, in a shape worth naming. It probed with http://10.0.0.5/push — a URL violating both arms at once, refusable by either alone. The assertion was real and the conclusion drawn from it was not: not a mirror, not vacuous, but under-determined — one probe, two sufficient causes, and the report naming neither. Now two legs isolating one arm each, demonstrated against a purpose-built host in three postures. Reported by a tier-1 host that found the identical defect in its own A2A path and declined to fix it, on the grounds that the obligation reached A2A only by inference — the judgement that made this a corpus defect rather than a host one.
  • The suite required a host to implement POST /v1/runs/{runId}:fork in order to say it had not. Eleven call sites handled 501 and none in the replay family handled 404 — the weaker signal treated as a skip, the stronger one as a defect. One rule now in conformance/src/lib/fork-availability.ts, whose negative controls are the load-bearing tests: every status the helper accepts turns an assertion into a skip. Root cause was narrower — replay-fanout-suppression was the only replay scenario with no capability gate at all — and ten bare early-returns across the family now say why, since a bare return in a file whose other tests assert leaves no trace at all.
  • RFC 0154's three unresolved questions are resolved, and two were already answered by the RFC's own body. No delegation-proof format is mandated (mandating SPIFFE or any other would select for a deployment shape, and the §20 seam cannot carry credential material — an unfalsifiable MUST); DPoP stays optional and advertised, since FAPI 2.0 approves both it and mTLS and each dies on the other's deployment; and the provenance envelope is DSSE + in-toto Statement with a separate OpenWOP predicate, because SLSA Provenance describes how an artifact was built and a certification bundle is a test-execution attestation. §E is amended where it conflated envelope with predicate, and workload-identity-cryptographically-bound is now recorded as structurally un-witnessable rather than left for someone to discover.
  • docs/EVIDENCE-DISCIPLINE.md gains practices 10 and 11, both from peer sessions. Practice 10: a test that recomputes the thing under test is a mirror, not an oracle — it can only fail if the function contradicts itself, which is the one thing that is not wrong with it, with the refinement that well-formed and correct are different assertions and only the second needs an independent recipe. Practice 11: re-measure the inference, not only the input — a register row whose measurement stayed true while the clause it depended on was spent by another route, so re-running the check returns the same answer and still supports the wrong conclusion. The crosstalk skill also gains a parsing invariant after a near-miss in which a wrong instrument produced output indistinguishable from the defect it was about to report.
  • Honest non-graduations and known-red surfaces. RFC 0111 stays Active — no steward host runs real orchestrator-loop model turns and the RFC forbids the mock supervisor from advertising contextBudget, so it takes the deferral rather than claiming a witness. RFC 0121 stays Active under a steward hold reaffirmed when asked directly. RFC 0158 stays Active; its recovery rows carry operator preconditions no unattended run satisfies. docs/KNOWN-LIMITS.md now lists all four open RFCs, having carried RFC 0043 as Active for the seven weeks since it was Accepted. The Conformance Soak workflow has been red on main since at least 2026-08-12 with zero successes in its last 100 runs — a host-lifecycle failure in that harness rather than a corpus defect, and unfixed; it is recorded here rather than passed over because a scheduled job nobody reads is the same defect class as everything above.

1.9.0

2026-08-23

the cycle that audited its own authority

No new RFCs and no Active → Accepted promotions (Accepted 153, Active 4, Draft 1). This cycle's defining event was governance rather than wire: a project-wide rule was found binding the corpus that no human had decided, and repairing it exposed that the rule was typical rather than exceptional. Alongside it, the adoption surface the protocol most needs was finally built. All wire shapes additive per COMPATIBILITY.md §2.1.

  • A freeze had been binding the project for nine days with no decision behind it. RFC 0147 §A.1 forbade new optional wire capabilities. It was authored in one agent session, promoted to Accepted in another 23 hours later, attributed to the steward — who, asked about it, had never heard of it. It had already deferred RFC 0158's capability, parked three compensation gaps, and been cited as authority in nineteen files.
  • Its exit condition had been satisfied from the start. §A.1 exits "when Workstreams 1–3 are Accepted and every Critical risk is Closed or transferred." Workstreams 1–3 (RFCs 0148/0149/0150) reached Accepted on 2026-08-12 — the same day RFC 0147 itself was accepted. Every artifact quoting that clause as pending, including RFC 0158 §E, had the wrong half. The freeze was held entirely by the Critical-risk condition.
  • All six Critical rows dispositioned against re-verified evidence, and one premise was simply false. R1 and R2 Closed; **R9 Closed because its sweep read "no host advertises compensation.supported" and a host did, and did at the time — live discovery carries the family and the published bundle records four compensation scenarios passing across 156 assertions. R3, R12 and R14 transferred to named tracked surfaces, all three re-verified as genuinely open and none closable by repository work. §A.1 is now struck through and marked SPENT, and every operational reference to it is gone from the working surfaces; compensation.md and RFC 0158 §E now argue their positions on their merits** instead of citing a freeze.
  • §A.1's own drafting was defective, and fixing it was a strengthening. It offered Closed or "transferred to an embargoed advisory" — unreachable for risks the project deliberately discloses. Embargoing R3, R12 and R14 would have made the project less transparent to satisfy a clause whose purpose is rigour, and R14 (no independent maintainers) is not a vulnerability at all. Now "a named tracked surface": a public surface is auditable by anyone; an advisory by no one outside the project.
  • The count that gated the freeze was miscounting in both directions. generate-assurance-status.mjs matched the bare substring closed anywhere in a risk's status cell. **RFC 0151 R1 — "Compensation executes twice", Critical, status "Open … unwitnessed" — had been counted CLOSED since 2026-08-16** because its cell mentioned "(G1 closed 2026-08-16)": a different item's closure. And a row saying a risk "cannot be closed by repository work" was counted closed by saying so. Now an explicit marker plus a negation guard, with transferred reported separately, pinned and sabotage-checked.
  • The freeze was not exceptional — it was typical (docs/WAIVER-AUDIT-2026-08-20.md). The bootstrap waiver grants additive RFCs, Draft → Active. Five confirmed RFCs — 0147, 0148, 0149, 0150, 0156 — are non-additive and reached Accepted: the umbrella, all three Workstream children, and the governance RFC defining the claims policy. RFC 0147 additionally forbids waiving its own class (§A.6) and was waived while affecting four of the surfaces it names. Six further RFCs are recorded as unclassified rather than as violations.
  • The ledger that exists to make waivers auditable was missing half of them, and now cannot be. 26 rows against 41 derived — the twenty-one absent included the entire program spine. scripts/check-waiver-ledger.mjs (step 6 of openwop:check) fails naming the missing set, deriving waivers with the literal same predicate as the assurance generator so the two cannot disagree about what a waiver is. Gated, not generated, deliberately: the rationale column is judgement no script can derive, and the twenty-one seeded rows carry no rationale rather than a reconstructed one — nobody wrote down why, and a plausible reconstruction would read as a decision that was made.
  • Chasing the safety-fix cohort found a stricter gate than the waiver one. Four of the five invoked COMPATIBILITY.md §3, which requires a 90-day public window or an embargo. RFC 0147 had 23 hours, so §3's own precondition was skipped — a stronger finding than a mis-cited waiver. Its shipping requirements were largely met, and checking corrected two of this project's own claims: RFC 0150 already had two runbooks (titled by subject, which a title grep missed) and RFC 0147 is owed none. The genuinely missing one is now written — RFC 0149 §A's /v1/v1 correction, whose hazard is that the bug was survivable: a consumer who worked around the doubled prefix has a workaround the fix converts into a new break, because the two corrections cancel and applying either alone is broken. A ### Security changelog section exists for the first time, and deliberately files no advisories — these were correctness fixes, and four fabricated advisories would signal vulnerabilities that never existed.
  • NEW docs/IMPLEMENT-CORE.md — the corpus's size, not its content, is why no independent implementation exists. 60 documents and ~220,000 words, with the minimal profile present but never merchandised. The guide opens by telling implementers not to read the corpus: openwop-core-standard is nine floor scenarios and four documents, with an explicit table of what to ignore and until when. It names the three things implementers get wrong — advertising an unwired capability, reading blocked as failure, and substituting instead of refusing — and asks for the bundle whatever it says.
  • NEW docs/EVIDENCE-DISCIPLINE.md — the conformance practices, written to be protocol-agnostic. Eleven practices for making a suite's results mean what a reader assumes, each stated with the defect that produced it: non-vacuous passes, typed dispositions carrying reasons, claims derived from the discovery document rather than declared, causable-not-merely-observable, a positive control inside every negative assertion, verifying a sabotage applied before trusting it, wall-clock refuting disposition, evidence attributing to a build, pointers checked with a two-way ratchet, a suite that may not be stricter than its spec, and comment-versus-code. Nothing in it depends on OpenWOP.
  • Four authoring disciplines landed in CONTRIBUTING.md, all earned rather than theorised. Ask whether the condition is causable, not merely whether the property is observable — a distinction that produced four wrong scenario designs in one day. Verify a sabotage is present in the tree before trusting its result — one silently failed to apply and nearly published the opposite of the truth from a green run. When a conforming host fails and a lenient one passes, suspect the oracle before the host. And a wrong comment that cites its authority is armoured: the reader follows the citation, finds a real section, and stops — it defends the error with the artifact that refutes it.
  • The webhook suite disagreed with the spec, its own OpenAPI, and itself. The reference verifier required v1= where webhooks.md mandates sha256={hex}, so the verifier a subscriber copies rejected every conforming delivery — surviving because the adversarial test signed and verified with itself, a closed loop that is self-consistent and wrong and therefore green on every host. Separately, a scenario required a subscriptionId the contract does not define, so a host implementing only the published contract failed at the first assertion while the reference host passed by returning both spellings. Fixed at suite 1.136.15, with the new pin building the header by hand from the spec.
  • replay.md gains a stated determinism model, and the largest unmeasured MUST NOT on that surface is now witnessed. The five caveats are recast as consequences of three sentences, with the model's one hole made findable. replay-fanout-suppression.test.ts witnesses host-initiated fan-out suppression — blocked in 41 ms on a default host, PASS in 7.6 s with an opt-in, and RED against a host patched to re-deliver. NEW SECURITY/threat-model-replay.md files the harm as integrity of externally-projected facts, not leakage: nothing escapes, and a correct re-emission defeats subscriber dedup by construction. Protocol invariant replay-fanout-no-refire added (184 rows).
  • RFC 0162 renumbered to 0158, and the first row of its conformance table is evidence. The gap 0158–0161 was reserving numbers for proposals the analysis rejected; a reader cannot tell a reserved number from a lost one. durability/poison-exhaustion asserts what failure-path cannot — not that failing work terminates, but that attempts stop — PASS in 4030 ms and RED against a host patched to retry post-terminal. Suite 1.136.11 → 1.138.1, 465 scenarios.

1.8.0

2026-08-18

the evidence-integrity cycle: what an artifact actually measured

No new RFCs and no Active → Accepted promotions (Accepted 153, Active 3 — 0035 / 0111 / 0121 held —, Draft 1 — 0038 Parked). This cycle repaired the evidence layer instead: a recurring defect in which a gate, a test, a spec sentence, a build, or a bundle reported, but not on what its reader assumed it measured. Five instances were found and closed, four of them by a host checking its own tree and disclosing the result. All wire shapes additive per COMPATIBILITY.md §2.1; one erratum classified safety-fix and one correction recorded as W3C-Class-3 conformance-affecting.

  • RFC 0151 compensation is finished as prose and witnessed on a deployed origin. §C lifecycle / §E operator recovery / §G threat model landed; UQ4 decided (irreversibleEffect, the author's statement that an effect has no inverse, mutually exclusive with compensation by schema); UQ5 resolved as a minimum, never a window — a host measured that its obligations have no retention purger at all, so a ceiling would be uncompliable without building one and would fight RFC 0053 dead-letter handling; G4 closed (a fork folds over its own inherited log; a non-terminal inherited plan is not inherited as an obligation — a fork is not a transfer of custody); and §F's "explicit authorization" clarified as an RFC 0049 decision bound to tenant/principal/action/planVersion, not the authored trigger policy. waiveRequiresApproval added (§B) with escalation as a floor a node-level false cannot lower.
  • A host MUST now refuse, at registration, a policy naming a trigger it does not fire (validation_error) — classified safety-fix, because silently accepting a guarantee that never fires surfaces only during the incident the policy existed for. Deliberately no advertisement surface: supportedTriggers stays frozen under RFC 0147 §A.1, and the freeze analysis is now recorded at the refusal itself, because relaxing the refusal would invert it.
  • The replay/re-emission contract was never ambiguous — the test was. replay.md caveat 5 has required recorded-fact re-emission since v1.2; RFC 0057 §D carried an implementation note that read as permission to suppress and conceded it met only half the rule. Note retired (recorded as W3C Class 3 — conformance-affecting, not editorial: a suppressing host is now clearly non-conforming where its status was arguable). The leg asserted inside a for loop over the replayed events, so a host emitting none passed vacuously — two hosts with opposite behaviour both stayed green for months. Both halves are asserted now.
  • NEW normative rule: host-initiated fan-out is an external effect. Every prior statement of "replay MUST NOT re-fire external effects" was scoped to nodes, leaving event-log fan-out unspecified — so a replay fork delivered webhooks asserting work it never performed, and a correct re-emission defeats subscriber dedup by construction (fresh envelope eventId). replay.md §"Host-initiated fan-out": suppress outbound delivery for re-emitted events, read replay-ness from the run not the event type, branch out of scope, unconditional. Found independently by two hosts — in the second as a written, reasoned exemption the rule negates — and deployed-wire the same day.
  • A certification bundle attributes to a BUILD, not a commit. conformance-certification.md claimed v1 bundles were "reproducible … against the same host commit"; a host rebuilt one commit and scored 283/22 against 303/2 (its image ran npm install, not npm ci). Claim retired; new gap G3: a bundle is evidence about one execution of one build, a verifier MUST re-run the same build or say which, and host.build / host.commit are now documented in certification-bundle-v2.schema.json — with the identifiers ranked by when each is available, since an image digest does not exist for a bundle baked into the image.
  • RFC 0150 §A lands in idempotency.md (v1.5), and the mismatch error finally has a canonical name. Record shape (requestDigest, pending|completed|retryable-failure|terminal-failure, lease owner/expiry), tenant from authenticated context never the body, atomic CAS reclaim, and the MUST NOT on host-generated identifiers sharing the caller keyspace. idempotency_key_mismatch is canonical; idempotency_key_conflict retired. Four artifacts had disagreed because the spec named no error and the leg asserted the 409 status but never a code.
  • storage-adapters.md gains §"Claim acquisition" — cited by four artifacts since RFC 0009, including a normative MUST, and never written. Atomic compare-and-set acquisition, holder-scoped heartbeat shorter than the TTL, expiry as the only reclaim authority, resume-on-startup, and the event-log invariants under claim transfer (a transfer is not a run event, because replay must not depend on which process executed).
  • Gates that reported without measuring. The RFC 0013 drift guard was step 4 of the soak job, so its failure masked the strict conformance suite and the multi-process durability scenarios for two months; it now runs in its own job, and a second mirrored region (RFC 0157 compensation) is compared. PROTOCOL-STATUS.md's reference-host table matched an unpadded header and had silently been empty. A profile floor can no longer cite a scenario that does not exist — or one that is corpus-only and therefore blocked in the published package forever.
  • Twenty-five suite defects (S22–S46), every one measured by a host rather than found by review. Highlights: the error-envelope schism decided flat (details.retriable); 44+14 readers that consulted only the deprecated capabilities wrapper; capabilities.mcp.serverUrls required by a scenario and forbidden by the schema; discoverOwnedTenant() reading a field no schema-valid host emits; the tarball_too_large probe that uploaded 60 MB into a proxy's 32 MiB ceiling instead of inflating a 64 KB gzip; and eight files whose softSkip sat after ctx.skip() and so never recorded a reason.
  • RFC 0147 program items closed: RFC 0155 §C extension registry 81 uncovered → 0; RFC 0156 §F assurance manifest + claims gate and §G single-source security SLA; RFC 0154 §E provenance verified from a clean checkout and §B negative chain legs; RFC 0149 UQ3 measured against a live sibling host. Hand-typed tallies are now gated against the tree.
  • Host evidence, stated at the strength it holds. openwop-app reached deploy #7 with RFC 0151 and RFC 0153 §B/§D/§E deployed-wire and openwop-memory certified; MyndHyve reached bundle #14 with executed-fail 0 and 13 profiles certifiable. Values withheld with reasons are recorded as such — replay.sideEffectSuppression stays none because "do not perform" and "resolve the recorded outcome" are different obligations, and a suiteVersion pin is recorded as measured, never rounded up.
  • Conformance suite 1.123.0 → 1.136.11 (published), +6 scenario files → 463, invariants 181 → 183. No net-new scenarios since the last suite tag, so this corpus tag re-publishes nothing.
  • Known red, stated rather than discovered: the Conformance Soak workflow's SQLite host lane is failing. That lane was invisible for two months behind the drift guard and is red because the strict suite now actually runs; the backlog is triaged but not cleared, and it is deliberately not silenced with profile opt-outs. The drift-guard and registry jobs are green.

1.7.0

2026-08-16

the integrity-program cycle: non-vacuous certification, versioned A2A/MCP composition, and the first host witnesses (RFC 0146–0157)

Lands the RFC 0147 protocol-integrity program end to end: RFCs 0146–0157 reach Accepted (Accepted 141 → 153, Active 3 — 0035 / 0111 / 0121 held —, Draft 1 — 0038 Parked), certification stops being able to lie by omission, A2A 1.0 and MCP 2026-07-28 get profiles + suite peers + real host witnesses, and every carried item is written into its RFC rather than implied. Suite 1.72.2 → 1.123.0 (412 → 457 scenario files; 34 net-new), invariants 160 → 175, prose docs 52 → 60, schemas 63 → 80. All wire shapes additive per COMPATIBILITY.md §2.1 except the three recorded safety-fixes (RFC 0148 evidence, RFC 0149 §A/§C, RFC 0150 §B), none of which moves a shape a conforming host emitted.

  • RFC 0147 program Draft → Accepted (0147–0156) + RFC 0157 errata + RFC 0146 provenance. Nine child RFCs each carry their own acceptance gates; seven of thirteen program annotations that said "carried" about landed work were corrected; criteria 2, 11, 12 checked; all twenty gap/risk registers dispositioned (131 rows). Externally gated criteria (audit, second maintainer, Tier-3 host, independent assessment) remain open by their own text. RFC 0146 contractProvenance Active → Accepted with both halves witnessed on one wire.
  • Certification can no longer read silence as a pass (RFC 0148). Requirement ledger (executed-pass|executed-fail|skipped|inapplicable|blocked per requirement, assertionCount witnesses), certification bundle v2 with a required blocked total, --certify exits 3 on an unclassified return, consumer verifier verifyBundleV2, evidence scrubbing (SR-1 canary), a floor for every catalog profile (discovery-conditional for openwop-replay-fork, runtime-derived for openwop-node-packs), and every named scenario on disk (conformance-execution-witness, conformance-advertised-seam-required, certification-bundle-non-vacuous, certification-bundle-redaction). Bundle v1 sunsets for new claims 2026-11-10 (version-negotiation.md runbook). The historic in-memory bundle was invalidated and all four reference hosts reissued as v2 (openwop-examples#14).
  • The core-standard floor named a file that never existed. audit-log-verification.test.ts had sat in PROFILE_FLOOR_SCENARIOS since RFC 0088; the ledger surfaced it on its first end-to-end run. Removed by the floor's own no-soft-skip rule (eight-file floor); core-standard-manifest.json regenerated.
  • Scenarios say why they return early. 84 silent-return files → softSkip(kind, reason) / seamAbsent (blocked by default, FAIL under OPENWOP_REQUIRE_BEHAVIOR=true; a 403 is no longer a pass); a zero-assertion file with no reason is blocked, never a pass; behaviorGate records inapplicable/skipped into the ledger; the advertise-vs-opt-out MUST NOT throws instead of warning.
  • Machine contracts reconciled (RFC 0149). servers[].url no longer doubles /v1 (44 operations resolved /v1/v1/*); protocolVersion grammar MAJOR.MINOR enforced in schema + predicate + prose; discovery examples unwrapped to root layout; discovery-canonical-family-no-shadow; lifecycle-coherence gate for RFCs ≥ 0147; normative examples extracted and validated (<!-- normative-example: … -->, 26 declared); spec/v1/operation-path-manifest.json + openapi-asyncapi-sdk-parity hold OpenAPI, AsyncAPI and the SDK path table to one resolution.
  • Effect identity, replay digest, split-brain (RFC 0150). Layer-2 identity retired the attempt component (safety-fix; zero deployed keys per the v1 inventory); semantic-request digest v2 (JCS) with golden vectors; crossRegion: fenced-effects and stale-owner vocabulary; three new invariants. Fenced-effect and pending-lease witnesses still wait for a fencing host.
  • Compensation profile (RFC 0151 + 0157). capabilities.compensation, node/settings policy (compensation-policy.schema.json), six content-free compensation.* events, compensationStatus on RunSnapshot, spec/v1/compensation.md, compensation-replay-no-refire, and the behavioural witness + §21 seams — blocked until a host advertises the family (openwop-app ADR 0554 P2 merged; advert pending). RFC 0157 lets chain fragments carry compensators (carryCompensation).
  • A2A 1.0 and MCP 2026-07-28 (RFCs 0152/0153). Versioned protocolVersions/preferredVersion/profiles on both families; full 1.0 translation table (from a2a.proto@v1.0.0) and 2026-07-28 mapping incl. MRTR, cacheable lists, extension opacity; legacy profiles named + time-bounded; dual-era fake peer and fake server; negotiation, no-silent-downgrade, card/runtime, peer-authority, stateless, MRTR, cache-scope, auth-boundary legs; header-body agreement precedes revision selection; header-less Agent Card = 0.3 while legacy is advertised. First non-vacuous host witnesses on openwop-app (ADR 0552 P2 / 0553 P2, local boots): all four A2A host legs and 15/16 MCP files under strict. Six named invariants registered on those witnesses.
  • Workload identity and delegation (RFC 0154). workload-identity.schema.json, auth.workloadIdentity with per-section gating, delegated actor chain, sender constraint, versioned/optional OTel identity mapping, SECURITY/threat-model-workload-identity.md, seam §20 and the behavioural witness — 17/17 under strict on openwop-app ADR 0556 P3 (merged, local boot).
  • Core profile discipline (RFC 0155). openwop-discovery-core canonical / openwop-core deprecated alias (both-or-neither; bundles report the alias in aliases), generated core-standard manifest, extension registry with a measured coverage block (5 covered / 81 uncovered / 4 core), unqualified-claim vocabulary.
  • Doc sweep (RFC 0147 criterion 11). GOVERNANCE.md (single security-response policy, bundle-v2 evidence rule), SECURITY.md (counts, what the invariant gate proves, audit state), COMPATIBILITY.md (measurement vs claim, safety-fix record), ROADMAP.md, INTEROP-MATRIX.md (evidence vocabulary; reference-host rows re-measured; versioned-composition witnesses as local boots), docs/KNOWN-LIMITS.md, docs/CERTIFICATION-BUNDLE-INVENTORY.md.
  • Honest corrections this cycle. A host-side overstatement about openwop-app passing 0152/0153 §B legs withdrawn (a 403 early-return); the suite's LLM cache-key contradiction fixed after a host went red for being right; the MRTR fixture posted chain-shaped edges (my finding against the host retracted); core.a2a.invoke was a node no host shipped → conformance-reserved core.conformance.a2a-invoke; six scenarios that threw at import in the published layout; the external-host outreach that was never sent; RFC 0121 hold reaffirmed as a decision.
  • Steward holds unchanged. RFC 0121 (Active, UQ1 ToS), 0035, 0111, 0038 (Parked); the error-envelope schism ({error:"code"} vs {error:{code,retriable}}) is decided in the next cycle under an architect pass, not here.
  • Conformance suite 1.72.2 → 1.123.0, published at openwop-conformance/v1.123.0 (the interim minors exist as commits only). SDKs release from openwop-sdks: @openwop/openwop 1.7.0 / openwop-client 1.6.0 (RunSnapshot.compensationStatus), Go unchanged at 1.5.0.

1.6.0

2026-08-11

the enforcement-surface + host-witness cycle (RFC 0136, 0138, 0142–0145)

Closes the capability-declaration and witness-discipline arc opened after 1.5.0: six RFCs reach Accepted, each on a non-vacuous reference-host witness (openwop-app under OPENWOP_REQUIRE_BEHAVIOR=true), and the corpus's recurring defect — a normative claim whose enforcement surface was never declared — is closed at the capability-discovery layer. Every conformance change this cycle was validated by running-and-proving (sabotage + run-log checks), never by a green. All wire shapes additive per COMPATIBILITY.md §2.1.

  • Six RFCs graduated to Accepted, each host-witnessed. 0136 (WorkflowVariable.format advisory hint), 0138 (pack vendor-extension hatch), 0142 (store-gated artifact.created emission), 0143 (tool-result trust is monotone), 0144 (which capability families the core schema declares), 0145 (registrationSource per-type facet). 0136/0143/0144 went Draft → Active → Accepted within the cycle; 0145 filed Active → Accepted. Each waited on a real host witness, not a version bump.
  • RFC 0144 — the capability-declaration classes, and the §A rule is now machine-enforced. Declares five capability families (artifactTypes, aiEnvelope, agentRuntime, forms, promptLibrary) at the plain discovery root, each carrying a wire-binding MUST the core schema had never declared. The partition rule (declare a §host.<name> section iff it binds a wire artifact) is enforced by check-capability-declaration-classes.mjs against a reviewed ledger (25 declared / 11 extension). Gaps G1 (which resolver arm a host lands on), G2 (duplicate §host sections), and G3 (rule enforcement) all closed. capabilities.schema.json 83 → 88 properties.
  • RFC 0145 — registrationSource per-type facet. A new OPTIONAL discovery facet on artifactTypes.types disclosing whether a type's schema-resolution regime is pack-backed or host-registered — the MUST/SHOULD asymmetry that was normative but invisible. Witnessed truthfully (the advert derives from the same registry field the emission reads, so the two surfaces cannot drift) and reproducibly against the adopter's pinned suite; G1 closed.
  • RFC 0143 — tool-result trust is untrusted-by-default and monotone. States as a meet-semilattice the general rule that ~ten point-invariants already instanced: content entering model context is untrusted absent a named basis for trusted, and no transformation (summarize, store-then-recall) launders it — the FRMD-F1-1 laundering path, previously unforbidden in general. New tool-result-trust-monotone protocol invariant (SECURITY 159 → 160); both halves host-witnessed.
  • RFC 0136 — WorkflowVariable.format, an advisory presentational hint. One optional string on WorkflowVariable; in RFC 0124 deferred mode a host MUST copy a chain parameter's format onto the minted variable (req 7) and MUST NOT validate a value against it (req 3, advisory). Witnessed via a real deferred-expand seam drive (B1) + a portable fixture run (B2), both sabotage-proven — the propagation MUST was a spec-MUST the reference host had been silently violating.
  • RFC 0142 — the store-gated artifact.created emission witness. Leg B went red first, on a live wire defect: the host emitted artifactTypeId and neither required field, so every artifact.created since its RFC 0071 support shipped was off-contract. A test written against the implementation had been green for its whole life; only a leg comparing a real emission to its canonical schema caught it. The scope of store: true (universally quantified over a type's artifacts, not "at least one path") is now stated normatively.
  • RFC 0138 → Accepted. The pack vendor-extension hatch: the motivating migration shipped with the load-bearing x-openwop-app.canvas extension retained, and the hatch's own narrowness legs ran host-side in strict mode.
  • Conformance suite — a batch of scenarios that rejected a conformant host, each caught by running not grepping. Five stale-assertion defects (1.66.0 → 1.67.1); webhook register tenant-scoping + signature headers conformed to webhooks.md v1.1 (1.68.1/1.68.2); the handoff-schema fixture rebuilt to actually fire the gate + gated on agents.manifestRuntime.handoffValidation (1.70.2); the Markdown link-check now walks the git index, not the working tree (1.70.3); the RFC 0136 §B legs strengthened from advert-tautologies to real drives + the portable B2 fixture (1.72.1/1.72.2).
  • SECURITY — SSRF relaxation-flag scoping (threat-model §4.8). A config antipattern (one relaxation flag disabling two surfaces' SSRF guards) recorded as a SHOULD + reviewer heuristic with a declared enforcement surface — deliberately not a scenario-less MUST-NOT, which would be the exact RFC 0144 defect written into the SECURITY doc.
  • Packaging — the vendored contract is addressable. schemas/CORPUS-STAMP.json + new check-npm-pack-contents.sh pins let a host detect a stale hand-copied schema as a lockfile fact rather than a silent green; conformance/README.md §"Resolving the contract" states the guidance (depend on the package, don't hand-copy). Narrows RFC 0145 G2.
  • Conformance suite version. @openwop/openwop-conformance advanced 1.66.0 → 1.72.2 this cycle (412 scenario files). No net wire-shape change; consumers pin the new version to install the corrected scenarios + the new witnesses.
  • Honest non-graduations. 0035 (needs a non-steward host running untrusted packs), 0111 (needs a real orchestrator-loop-turn witness), and 0121 (needs legal/ToS clearance for a named provider) stay Active — each blocked on evidence a steward cannot manufacture.
  • Counts. Accepted 141 · Active 3 · Draft 1 (145 RFCs, excluding the 0000 template; Active = 0035, 0111, 0121; Draft = 0038 Parked). Six Active → Accepted this cycle.

1.5.0

2026-08-08

the trust-boundary + replay-integrity cycle (RFC 0132–0142)

Locks down who may act (anonymous actors), what content may be trusted (pack-authored strings, card inputs, vendor extensions), and what a replay may touch (nothing outside the recorded outcome). Nine RFCs reach Accepted (0043, 0132, 0133, 0134, 0135, 0137, 0139, 0140, 0141), two land Active (0138, 0142), and the conformance suite's own honesty was a first-class deliverable: gates that skipped silently now fail loudly. All wire shapes additive per COMPATIBILITY.md §2.1. RFC counts move to Accepted 135 / Active 5.

  • Anonymous-actor authorization (RFC 0132, Accepted 2026-07-22). A third explicit principal kind for tool-enabled public agent surfaces — no default baseline, no secret reach, SSRF-guarded egress, write-egress gated, opaque audit. Landed reference-impl at Active, graduated protocol-tier same-day on the openwop-app witness (5 gated scenarios 10/10 under OPENWOP_REQUIRE_BEHAVIOR=true). Five SECURITY invariants ride with it.
  • Workflow-chain composition — sub-chains + produced variables (RFC 0133, Accepted 2026-07-23; RFC 0134 + 0135 alongside). Sub-chain co-expansion with bounded depth and child-tenant scoping (two invariants), EdgeCondition gains truthy/falsy operators (0134), and chains can be marked internal — omitted from the default gallery without becoming an authorization boundary (0135, witnessed on openwop-app).
  • Form-content packs — a sixth declarative pack kind (RFC 0137, Draft → Active → Accepted, 2026-08-05). kind: "form-content" with templates[]/fields[]; the field-type vocabulary is byte-identical to chat-card inputs[].type so neither can widen alone, and unrecognized vendor.*/x- types degrade to plain text. A same-day implementer amendment corrected bounds and added the no-submission-routing rule: a template describes a form's shape, never where submissions go. Two trust invariants (pack-authored strings are untrusted; no routing surface). First pack live on the registry (155 → 156).
  • The RFC 0137 gate was unsatisfiable, then silent — both fixed, and the fix generalized. Every leg the RFC originally shipped was server-free, so its Active → Accepted gate could pass against a host that never implemented it; the behavioral leg now exists and gates on behaviorGate, which fails loudly in strict mode instead of soft-skipping. The G16 ruling landed with it: the canonical discovery key is the plain family name at the document root — three conformance helpers that read only dotted host.* keys had made schema-following hosts invisible.
  • Vendor-extension hatch on pack manifests (RFC 0138, Active 2026-08-06). host-extensions.md required unknown vendor-prefixed fields be treated as opaque while every manifest's additionalProperties: false made such fields impossible — the corpus mandated a behavior for a case it structurally forbade. 16 ^(x-|vendor\.) hatch sites across all eight pack manifests and the registry publication contract; a misspelled canonical field is still rejected; "ignore" is defined normatively (never render, execute, interpret, code-path-switch, or persist-for-later) with the pack-manifest-extension-opaque invariant. Corrected 2026-08-07: the RFC briefly recommended a persisted-id backfill; composing three documents shows that rewrites the run-event log and breaks :fork — superseded in place with a dated notice.
  • Extension opacity witnessed against a host (RFC 0139, Draft → Active → Accepted, 2026-08-07 → 08-08). Presence assertions cannot express opacity — a host that stashes an extension and interprets it later passes them trivially — so the witness is differential: identical installs with/without an unrecognized extension must yield identical registration projections. Stub-measured (a violating host passes every presence leg and fails only the differential), then accepted on openwop-app's five-leg run from a clean npm install, where the legs found two defects the host's own passing tests could not: a loader that never validated the manifest schema, and a stale-registry false-pass that was the differential's own failure mode one layer up. The G14 flip landed with it: three long-soft-skipping RFC 0071 scenarios now fail strict mode when advertised-but-unwired — advertise-and-skip is the only combination that can lie.
  • Replay side-effect suppression (RFC 0140, Draft → Accepted, 2026-08-08). A replayed run MUST NOT re-fire its effects: the replay.sideEffectSuppression capability, the recorded-outcome-or-fail-closed MUST, and the replay_source_missing typed failure — Layer-2 idempotency provably cannot cover a fork (new runId ⇒ disjoint key space). Accepted on the openwop-app witness (source effectCount: 1, replay 0, cancelled-source fail-closed; eight per-fix sabotages). Honest correction same cycle: the RFC's motivation claimed v1 constrained nothing here — replay.md caveat 1 had always MUST-ed it; corrected in place.
  • Legacy artifact-type identifiers (RFC 0141, Accepted on landing, 2026-08-08). Pre-canonical ids (doc.one-pager) were never wire-conformant — no grandfather clause, and no obligation to migrate; a migrating host MUST NOT rewrite historical artifact.created.artifactType (fork determinism, or silent demotion to unregistered on a green run); the conformant shape is a permanent read-side alias resolved everywhere registration is decided, validation included; an alias is a host shim, not a conformance claim. Accepted on a witness that preceded the RFC — openwop-app's alias map, source-verified, whose own commit declined to claim conformance until the corpus said this.
  • The store-gated emission witness (RFC 0142, Active 2026-08-08). The corpus's only artifact.created emission MUST hangs off the store facet, which no leg checked — unreachable in both directions, proven when the reference host falsified its own "zero emit sites" negative and turned out to have emitted correctly for months, uncredited. A real-run seam + event-log assertion closes it; Active until a host advertises store: true honestly — acceptance MUST NOT be manufactured by advertising to graduate it.
  • Registry + extension policy ratified (RFC 0043, Accepted 2026-07-07). The one-stop governance policy — namespace reservation, submission/yank/key-rotation, IPR posture — live at docs/governance/registry-policy.md and cited as authoritative from normative spec.
  • Conformance suite 1.54.0 → 1.67.0, 400 → 408 scenario files — published continuously on per-package openwop-conformance/v* tags through 1.66.0; 1.67.0 rides this corpus tag. New always-on legs pin the pack-manifest hatch, legacy-id rules, and artifactCreated.artifactType (required); behavioral legs cover form-content instantiation, extension opacity, replay suppression, and store-emission. behaviorGatePresent joins the gate library so seam-absence narrows types without discarding null-safety.
  • SECURITY surface 149 → 159 invariants (129 protocol-tier). +10, all protocol-tier: the five anon-actor-* rows (0132), sub-chain-expansion-bounded + sub-chain-child-tenant-scoped (0133), form-content-pack-string-trust-boundary + form-content-template-no-submission-routing (0137), and pack-manifest-extension-opaque (0138).
  • Honest corrections are part of this release's surface. The month-red Conformance Soak was un-wedged (its drift gate compared 33 KB against a 4.5 KB mirror; a sentinel now scopes the genuinely-shared core — the residual doc-comment delta lives in openwop-examples); RFC 0138's backfill recommendation and RFC 0140's false motivation were corrected in place with dated notices; the RFC 0137 G15 register entry was rewritten when its claim proved wrong; the README count-sync script's three dead regexes and the two-months-rotted RFC status index were fixed and are now gated (#903/#904).
  • Release hygiene, stated rather than hidden: no v1.4.0 corpus tag was ever cut — the 1.4.0 release existed in CHANGELOG form only. The npm-publish trigger is the tag, not the version bump; that habit gap cost two downstream stale-install incidents this cycle before being fixed. v1.5.0 resumes corpus tagging.

1.4.0

2026-07-07

agent-manifest `role` + the Skill profile (RFC 0131 `Accepted`)

Distinguishes a composable, task-scoped Skill from a top-level assistant agent, first-class on the agent manifest. One RFC reaches Accepted; wire shape additive per COMPATIBILITY.md §2.1 plus a schema-encoded safety-fix that binds only opt-in manifests. RFC counts move Accepted → +1 (0131).

  • RFC 0131 — agent-manifest role + the Skill profile (Accepted, 2026-07-07). Adds an additive OPTIONAL AgentManifest.role ("skill" | "assistant") — EXPLICIT, never inferred (handoff presence does NOT imply skill; an assistant MAY ship a typed entry contract). An absent role is unconstrained — byte- and behavior-identical to today, no profile binds it. A manifest that opts in with role: "skill" binds the Skill profile, encoded in agent-manifest.schema.json as an if role==="skill" conditional: it MUST declare handoff and MUST constrain memoryShape to scratchpad-only (conversation/longTerm MUST be false/absent — persistent + multi-turn memory belong to the composing assistant/roster agent, RFC 0039; a stateful worker undermines replay determinism, RFC 0041). A violating skill manifest fails schema validation at publish/install (pack_validation_failed) — a malformed manifest (RFC 0003 §C author error), deliberately NOT an RFC 0072 §C degraded[] runtime tier (whose "the host lacks a capability a well-formed agent wants" meaning stays crisp). agent-memory.md §D states the memory constraint adjacent to §C with an explicit reject-vs-degrade contrast; node-packs.md §agents[] documents the field. Because enforcement is universal schema validation (no capabilities.* advert, no host behavior), there is no per-host interop variance — the always-on agent-manifest-role-profile.test.ts is the universal witness. Draft → Accepted via the bootstrap single-maintainer comment-window waiver (CONTRIBUTING.md §"Bootstrap-phase notes"), architect-reviewed by the steward (explicit role opt-in + schema-encoded reject were the two review-driven revisions). Reference host: openwop-app ADR 0312 (Phase-0 marketplace "Skill" label + Phase-2 worker-memory normalization).
  • SECURITY invariants +1 (protocol-tier +1). Adds agent-skill-profile-stateless (RFC 0131 §B) with its matching always-on public conformance test.

1.3.0

2026-07-07

the CDP Track-2, workflow-chain-pack, and connector-grouping graduation cycle (RFC 0121–0129)

Closes the RFC 0121–0129 cycle plus the RFC 0013 workflow-chain-pack errata: eleven RFCs reached Accepted (0114, 0116, 0117/0119, 0122, 0123, 0124, 0125, 0126, 0127, 0128, 0129) and four new ones landed their Active normative surface (0124, 0125, 0126, plus 0121 advancing to the at-own-risk implementation gate). The work clusters into CDP Track-2 (streaming/CDC triggers + purpose-propagation labels + data-residency), portable workflow-chain-pack parameter deferral + edge fan-in rules, data-parallel per-item dispatch, the self-hosted runner, and presentational connector-catalog grouping. All wire shapes additive per COMPATIBILITY.md §2.1; two safety-fixes (RFC 0013 substitution typing, RFC 0124 sensitive-param secret handling) close credential/portability leaks without relaxing any MUST. Corpus RFC counts move Accepted 113 → 124 / Active 8 → 4 / Draft 1 (0038).

  • CDP Track-2 — streaming/CDC triggers, purpose labels, data-residency (RFC 0127/0128/0129 Accepted). triggerBridge.sources[] gains stream (broker message) + change (warehouse CDC record) with op-carrying envelopes reusing the RFC 0099 SSRF-guard + content-free trigger. events (0127). An OPTIONAL permittedPurposes: string[] label rides TriggerEvent + the A2A metadata.openwop. namespace; an advertising host MUST re-emit onward (MAY narrow, MUST NOT widen), and []-labelled data MUST NOT be forwarded (0128). An OPTIONAL capabilities.dataResidency {regions[]} + residency:{region} on POST /v1/runs makes region admission control a falsifiable MUST (accept iff advertised, else residency_unavailable, create no run); physical byte-confinement stays a §4 declared-intent SHOULD (0129). All three graduated single-witness on the tier-1 reference host, steward-curl-verified on the wire; each carries a named G4 tier-2 gap.
  • Workflow-chain-pack portability — deferred parameters + edge fan-in (RFC 0124/0125 Accepted). RFC 0124 adds an OPTIONAL workflowChainPacks.deferredParameters mode that keeps chain {{params.*}} overridable per run without non-portable runtime tokens — the host materializes chain parameters into variables[] + rewrites to spec'd bindings, replay-deterministic via RunSnapshot.variables. RFC 0125 mirrors triggerRule onto FragmentEdge for pack-declared fan-in / error-routing. Both single-witness on the reference host.
  • Data-parallel per-item dispatch (RFC 0126 Accepted). An OPTIONAL index-aligned nextWorkerInputs[] on NextWorkerDecision fans ONE childWorkflowId over N runtime items with distinct inputs, over the RFC 0022 projection, fail-closed on a non-advertising host or a length mismatch (validation_error, no child dispatched), replay-safe on :fork. Single-witness on a real core.dispatch executor.
  • Self-hosted runner (RFC 0122 Accepted). New OPTIONAL selfHostedRunner capability: a hosted control plane routes per-step dispatch to a user-controlled runner that dials OUT (SSE receive + POST result) and holds local credentials the host cannot reach; the host stays the sole orchestration/replay authority. Graduated tier-1 (openwop-app, runner_unavailable=409) + tier-2 (MyndHyve, =503) — the 409/503 split demonstrates the #815 envelope-not-status contract cross-host.
  • Front-end plugin packs + prompt-prefix cache + A2UI deltas (RFC 0117/0119, 0116, 0114 Accepted). Sandboxed cross-origin-iframe kind:"frontend-plugin" loader with deny-egress CSP + host-RPC allowlist (0117/0119); portable secret-free cachePrefixId prompt-prefix caching with tenant-keyed cross-tenant isolation (0116); OPTIONAL RFC 6902 A2UI delta-frame transport over the unchanged recorded ui.a2ui-surface envelope (0114). All single-witness, steward-curl-verified.
  • Connection-pack provider vendor grouping (RFC 0123 Accepted). OPTIONAL presentational provider.vendor string groups a connector catalog by commercial vendor; gates no capability and carries no wire behavior, so the schema-validation legs are the witness. Evidence-upgraded to host-served real grouping on the reference host.
  • Subscription-reuse provider auth mode (RFC 0121 Active, at-own-risk gate). Adds "subscription" to the aiProviders.authModes enum + the protocol-tier subscription-credential-user-scope-only MUST (credential_scope_forbidden on tenant/workspace bind). UQ1 (provider ToS legality) remains UNRESOLVED; a steward at-own-risk waiver permits implementation of the acquisition-bearing surface at operator/end-user risk — a risk waiver, not a legal clearance. The §B.8 request-scope rejection rail is steward-curl-witnessed on the reference host; the invariant's storage-resolvability clause is inherently host-test territory (host-covered by openwop-app #1444) — see docs/KNOWN-LIMITS.md §"Behavior tests too coarse".
  • Two safety-fixes (COMPATIBILITY.md — no MUST relaxed). RFC 0013: a value that is exactly one {{params.x}} token resolves to the raw typed value (objects/arrays survive their JSON type), node.inputs are preserved verbatim through expansion, and a parameter-distinct persisted-identity rule prevents silent overwrite. RFC 0124 §Security: a sensitive param MUST materialize as source:"secret" (BYOK-resolved, never plaintext), is deferrable only in a prompt-body position, and fails closed (sensitive_param_not_deferrable, 422) everywhere else — closing a run-time RunSnapshot/at-rest leak.
  • RFC 0013 errata (doc/test-only). Expanded node ids are unbounded — hosts MUST NOT cap id length (WorkflowNode.id has no maxLength); the host-expansion test seam decoupled onto a conformance-only workflowChainPacks.hostExpansionSeam flag + the workflow-chain-sample fixture bundled, so RFC 0013's Phase-3 host-expansion acceptance item is witnessed 6/6 harness-tier.
  • SECURITY invariants 145 → 148 (protocol-tier 115 → 118). Adds subscription-credential-user-scope-only (0121 §B.8), runner-credential-non-transit (0122, BYOK SR-1 parity), and runner-output-untrusted-transport (0122, <UNTRUSTED> fencing) — each with a matching public conformance test.
  • Conformance suite 1.46.0 → 1.54.0 (independent per-package cadence; scenario files 384 → 387). Ships the gated scenarios for every graduation this cycle: aiproviders-subscription-scope, self-hosted-runner, dispatch-per-item-input, workflow-chain-deferred-parameters, trigger-stream-cdc-sources, purpose-propagation, data-residency-admission, plus the RFC 0013 host-expansion fixture. Already published incrementally on npm.
  • Honest scoping carried on the public credibility surface. RFC 0111 (contextBudget) stays Active — the tier-1 host honestly opted out (skeleton orchestrator loop, no real model turns; advertising would be a dishonest wire claim). Every graduation this cycle names its carried-forward tier-2 gap in INTEROP-MATRIX.md + docs/KNOWN-LIMITS.md; none is claimed as independent-organization dual-witness.

1.2.0

2026-06-30

the token-economy, front-end-plugin, and parallel-fan-out graduation cycle (RFC 0109–0120)

Closes the RFC 0109–0120 cycle: seven RFCs reached Accepted (0109, 0110, 0112, 0113, 0115, 0118, 0120) and five new ones landed their Active normative surface (0111, 0114, 0116, 0117, 0119). The work clusters into a token / transport economy, sandboxed front-end plugin packs, parallel sub-workflow fan-out, and a pack-delivered credential-egress allow-list. All wire shapes additive per COMPATIBILITY.md §2.1; no v1 wire break. RFC counts move Accepted 105 → 112 / Active 2 → 7 / Draft 1 (0038).

  • RFC 0109 — Conversation-turn model provenance (Accepted, 2026-06-24). Additive OPTIONAL agent.model ({ provider, model }) on conversation-turn.schema.json recording which model produced a role:'agent' turn, gated on the new optional conversationTurnModelProvenance.supported capability. NON-SECRET / NON-PII by additionalProperties:false (the SR-1 guard — no credential/endpoint/prompt rides the stamp); read VERBATIM on :fork. Server-free shape scenario (conversation-turn-model-provenance-shape.test.ts). Draft → Accepted via the bootstrap single-maintainer comment-window waiver; reference stamp = openwop-app ADR 0124 Phase 2d.
  • RFC 0110 — Channel presence (Accepted, 2026-06-24). Additive OPTIONAL ephemeral channel.presence RunEvent (channel-presence-payload.schema.json) for type:'channel' conversations — present-member subject refs + optional per-member typing — gated on channelPresence.supported. Hosts MUST NOT persist it to the replayable log; it is replay/:fork-invisible (the load-bearing distinction from the persisted conversation.exchanged turn). Membership-gated (DEFAULT-DENY, CTI-1), NON-PII (opaque subject refs only). Server-free shape scenario; Draft → Accepted via the bootstrap waiver. Reference-host emit DEFERRED (a single-instance deployment is the natural first emitter).
  • RFCs 0112 / 0113 / 0115 — token + transport economy (Active → Accepted, 2026-06-27). Dual-witnessed vs published @openwop/openwop-conformance@1.43.0 (openwop-app reference rev 00332-gm2 + MyndHyve tier-2 revs 00511-len/00512-pej/00510-jiv), every gated scenario re-driven non-vacuously by the steward. 0112 compact tool projection — OPTIONAL ?view=compact on GET /v1/tools[/{toolId}] returning a lossy { tools: CompactToolDescriptor[] } with a self-contained input-schema subset (compact-tool-descriptor.schema.json) + toolCatalog.compactView. 0113 memory injection budget — OPTIONAL tokenBudget/rank/query on MemoryListOptions token-bounding the live injection read (rank:'relevance' delegates to memory.search, no new ranking primitive) + memory.injectionBudget; SR-1/CTI-1 hold by construction. 0115 run transport economy — OPTIONAL strong ETag + If-None-Match/304 + Content-Encoding negotiation on GET /v1/runs/{runId} + restTransport. Per GOVERNANCE.md tier-2 + reference-host evidence.
  • RFCs 0111 / 0114 / 0116 — token-economy surfaces (Active, 2026-06-26). Normative wire surface landed, host witness pending. 0111 context economy — OPTIONAL multiAgent.executionModel.contextBudget (transcript token budget + a summarization contract whose substitutions record a content-free context.summarized event with a replay-reused summaryRef). 0114 A2UI surface deltas — OPTIONAL host-side RFC 6902 delta-frame transport (a2ui-surface-delta-frame.schema.json, ?a2uiDelta=1) over the UNCHANGED recorded ui.a2ui-surface envelope; every A2UI invariant MUST hold post-patch. 0116 portable prompt-prefix cache — OPTIONAL secret-free cachePrefixId on ctx.aiEnvelope.generate + provider-scoped aiProviders.promptPrefixCache + cost-only cacheReadTokens/cacheWriteTokens usage fields, with tenant-keyed cross-tenant isolation (prompt-prefix-cache-cross-tenant-isolation) + replay invariance.
  • RFC 0117 — Front-end plugin packs (Active) + normative surface. Additive kind:"frontend-plugin" registry pack + OPTIONAL host.uiPlugins capability for portable sandboxed UI extensions: the wire owns the boundary, not a renderer — a mandated cross-origin-iframe isolation model (in-process loading is a protocol-tier MUST NOT), a closed postMessage host-RPC allowlist (ui-plugin/1, with an RFC 0059-style optimistic-concurrency version token), a deny-egress CSP, Ed25519 signing, and graceful degradation to RFC 0071 rendering. Lands the spec doc, frontend-plugin-manifest.schema.json + ui-plugin-message.schema.json, the four frontend-plugin-* invariants, and the gated frontend-plugin-packs.test.ts. Active → Accepted waits on the openwop-app reference host (ADR 0153 canvas editor) advertising host.uiPlugins.
  • RFC 0119 — Mechanism-neutral plugin isolation (Active; amends RFC 0117). Reconciles RFC 0117's property-level isolation prose with its const "cross-origin-iframe" schema pin: widens capabilities.uiPlugins.isolation to a categorical model (enum [cross-origin-iframe, wasm, process, container, vm] + ^x-host-*, reusing the RFC 0035 vocabulary, cross-origin-iframe the default) and reframes the frontend-plugin-isolation MUST-NOT as mechanism-independent. Strengthens §Host-RPC for non-browser transports (transport-guaranteed framing + per-channel ordering; for wasm, no host import beyond the single ui-plugin/1 entrypoint). Prose + looser-validation only (COMPATIBILITY.md §4); no envelope-schema change.
  • RFC 0118 — Parallel sub-workflow fan-out and join (Active → Accepted, 2026-06-28). Closes RFC 0007 §K3's deferred fan-out: additive fanOutPolicy:'parallel' + an optional joinPolicy (mode wait-all/quorum/first/race × onChildFailure collect/fail-fast/absorb) + maxConcurrency on DispatchConfig, emitting core.dispatch.fanOut/core.dispatch.join run-events with a replay-deterministic mergeOrder; gated on dispatch.fanOutSupported, default stays 'sequential'. Graduated on dual-witness discovery + behavioral evidence (openwop-app prod + MyndHyve tier-2 rev 00513-qib, steward-verified via the POST /v1/host/sample/dispatch/fanout seam: joinOutcome:"satisfied", 3 children, mergeOrder of 3; corroborated by MyndHyve's dispatch-fanout-parallel.test.ts 10/10). Honest tier-2 + reference-host, with the per-host joinModes/onChildFailureModes subset divergence the gating exists to surface.
  • RFC 0120 — Connection-pack apiHosts credential-egress allow-list (Active → Accepted, 2026-06-29). Closes the pack-delivered confused-deputy gap: a connection pack (RFC 0095) could declare a provider's auth endpoints but not the API host(s) its resolved credential may egress to. Adds the OPTIONAL provider.apiHosts array (strict bare registrable hostnames; IP/wildcard/port/single-label rejected) — REQUIRED at the manifest when reach:openapi and at the RFC 0045 binding site (conditional MUST, fail-closed), an eTLD+1 floor with dot-anchored suffix containment, AND-composing with (never widening past) the RFC 0079 audience guard. Two protocol-tier invariants (connection-pack-api-host-shape + connection-pack-egress-host-bound) with the gated connection-pack-apihosts.test.ts. Graduated single-witness tier-2 + reference-host: openwop-app ran the published 1.46.0 leg non-vacuously under OPENWOP_REQUIRE_BEHAVIOR=true (PERMITs graph.facebook.com, fails closed on evil.com/notfacebook.com/facebook.com.evil.com); MyndHyve honestly opted out of the brokered-egress arm.
  • SECURITY invariants 138 → 145 (protocol-tier 108 → 115). Adds the four frontend-plugin- invariants (RFC 0117), prompt-prefix-cache-cross-tenant-isolation (RFC 0116), and the two connection-pack- apiHosts invariants (RFC 0120); the A2UI invariant set (RFC 0114) MUST hold on the post-patch surface.
  • Conformance suite 1.37.0 → 1.46.0 — published independently on its own openwop-conformance/v* tags as each RFC's gated + always-on scenarios landed (compact-tool / memory-budget / run-transport / context-budget + summarization-replay / a2ui-delta / prompt-prefix-cache / frontend-plugin / dispatch-fanout / connection-pack-apiHosts). Per the 2026-06 repo split, this corpus repo publishes exactly this one artifact.
  • Honest scoping carried on the public credibility surface. RFCs 0111 / 0114 / 0116 stay Active (single-/no-witness — 0111 needs a real-orchestrator-loop host; MyndHyve architecturally opted out of 0114 + 0116); RFCs 0117 / 0119 stay Active pending a host advertising host.uiPlugins; RFCs 0118 / 0120 graduated on tier-2 + reference-host evidence, recorded as such — not independent-org dual-witness. Each is documented in docs/KNOWN-LIMITS.md + INTEROP-MATRIX.md.

1.1.9

2026-06-24

the modality + agent-interaction graduation cycle (RFC 0100–0108)

Closes the RFC 0100–0108 graduation cycle: eight RFCs reached Accepted on dual-witness (one single-witness, bootstrap-waiver) behavioral evidence, landing agent-authored UI, localized content, portable HITL approver routing, speech synthesis, real-time voice, durable A2A tasks, multi-party conversation, and a self-hosted / OpenAI-compatible provider class. All wire shapes additive per COMPATIBILITY.md §2.1; no v1 wire break. RFC counts move Accepted 96 → 105 / Active 3 → 2 (0035, 0043) / Draft 1 (0038).

  • RFC 0102 — A2UI agent-authored interface surfaces (Accepted, 2026-06-15). The optional, advertised core envelope kind ui.a2ui-surface — a declarative interactive UI a consumer renders with native widgets, routing user actions back without executing agent-supplied code (closed component tree, host-enumerated catalog, interrupt-resume / conversation-exchange confinement). Dual live evidence vs suite 1.26.0 (openwop-app rev 00204-v75 + MyndHyve, byte-identical core schema sha256 68f977c1…). New schemas/envelopes/ui.a2ui-surface.schema.json + five SECURITY invariants + a threat-model-prompt-injection.md §4.8 section. (Briefly amended to a vendor-namespaced kind on a blind first review (#715), then reverted to the core kind on re-review — cross-host portability is the property the core kind delivers.)
  • RFC 0103 — Localized content surface (Accepted, 2026-06-17). A capability-gated surface for durable authored structured localized content (pages → sections) that reuses the Stable i18n.md Accept-Language / Content-Language negotiation verbatim and adds only the data model + a per-section field merge (resolveSection: exact → language-family → base). New core spec spec/v1/localized-content.md, four schemas, a content capability block, OpenAPI delivery + admin paths, three protocol-tier SECURITY invariants. Dual non-steward host evidence (openwop-app + MyndHyve) vs suite 1.27.0.
  • RFC 0104 — Portable HITL approver routing (Accepted, 2026-06-19). Three optional advisory fields on the kind:"approval" interrupt payload (approverGroupRefs, approverRoleRefs, audience) + a top-level interrupt.approverRouting capability, so group / role approver routing is portable + capability-gated. The audience hint defaults to the resolved eligibility union and is fixed history on replay / :fork. Dual non-steward host evidence with honestly-divergent audience support (openwop-app + MyndHyve) vs suite 1.28.0.
  • RFC 0105 — Speech synthesis adapter (Accepted, 2026-06-21). Additive aiProviders.speechSynthesis: "supported" exposing ctx.callSpeechSynthesizer(...) → a binary audio asset (host-served url or inline base64 + mimeType), a verbatim parallel of ctx.callImageGenerator. Dual independent non-steward host evidence vs suite 1.29.0 on the identical locked envelope with honest provider / asset-arm divergence (openwop-app MiniMax / url rev 00265-wtx + MyndHyve Gemini / base64 rev 00280-dcs). Reuses existing SSRF + untrusted-media guards (no new invariant).
  • RFC 0106 — Real-time voice session profile (Accepted, 2026-06-24). Additive aiProviders.realtimeVoice profile: streaming transcription (ctx.callTranscriber), a streaming arm on ctx.callSpeechSynthesizer, and a voice.* turn-taking / barge-in run-event taxonomy (run-event types 100 → 107) with a distinct session-bound streamRef live-conduit handle. Per-capability graduation: the transcription core is dual-witnessed (openwop-app rev 00294-nhz + MyndHyve Gemini-multimodal rev 00503-row) vs suite 1.34.0; the optional synthesis / turnDetection / bargeIn arms stay single-witnessed + conformance-gated. voice-transcript-untrusted + voice-bargein-no-partial-leak are protocol-tier; voice-interim-not-durable + voice-streamref-tenant-bound stay reference-impl (their scenarios don't yet drive the adversarial leg — a conformance-hardening follow-on). A §B/§C return-primitive amendment corrected a surfaced replay-determinism hazard (node-facing ctx resolves a Promise + emits to the durable log, never a live AsyncIterable).
  • RFC 0100 — Async / durable A2A tasks (Accepted, 2026-06-24). Graduates the a2a async / durable composition surface (durableTasks + pushNotifications) on dual-witness evidence vs suite 1.34.0 (openwop-app rev 00299-hns + MyndHyve rev 00505-seh): a durable tasks/get over a real approval-gated run (input-required, taskId == runId) + push-config SSRF rejection, both serving a credential-less GET-resolvable A2A v0.3 AgentCard. Honest scope: streaming (tasks/resubscribe) is omitted by both — suite 1.34.0 ships no resubscribe gated subtest, so advertising it would be vacuous. a2a-push-egress-ssrf was already protocol-tier (no graduation).
  • RFC 0108 — Self-hosted / OpenAI-compatible provider class (Accepted, 2026-06-24). Additive optional aiProviders.selfHosted[] (a subset of supported[]) marking operator- / tenant-configured OpenAI-compatible endpoints, with truthful-advertisement (§A.2), endpoint non-disclosure (§A.3 / §D), and capability-non-inference (§B) rules + the protocol-tier self-hosted-endpoint-no-disclosure invariant. Single-witness close under the bootstrap steward waiver on the openwop-app conformance-harness witness (the appropriate tier for an intrinsically operator-private surface that MUST NOT be publicly exposed — the RFC 0035 harness-graduation precedent); production stays dark (OPENWOP_COMPAT_PROVIDER_ENABLED unset ⇒ selfHosted:[]). Passes both aiproviders-selfhosted-shape (always-on) and aiproviders-selfhosted-honesty (gated) non-vacuously vs suite 1.37.0.
  • RFC 0101 — Multi-party group conversation (Accepted, 2026-06-22). Un-parked + graduated via the /prd five-architect pass. Three additive capability-gated facts so N agents co-participating in one shared transcript are cross-host-observable: an optional participants: AgentRef[] roster on conversation.opened, a conditionally-required speakerId on agent ConversationTurns, and a multiPartyConversation capability block. A follow-on landed the capability-gated behavioral leg + a /v1/host/sample/conversation/multi-party/* conformance seam (RFC 0101 mints no normative client wire-route to open a council). Reuses roster-attribution-no-content (no new invariant).
  • RFC 0107 — Publishable declarative pack kinds (Active) + chat-card-kind correction (safety-fix). Lands the Active RFC 0107 (opened + flipped Draft → Active 2026-06-23): the published registry-version-manifest.schema.json gains a kind discriminator (node default) + per-kind declarative payloads (artifactTypes / provider / chains / prompts / cards), with runtime re-tightened by an if/then/else (executable kinds MUST carry it; declarative kinds MUST NOT) — every existing node manifest validates unchanged. Separately corrects a never-honored cross-RFC enum disagreement: the chat-card-pack kind is card (RFC 0071 authoritative), not chat-card; zero such packs were ever published (safety-fix).
  • SECURITY invariants 125 → 138 (protocol-tier 99 → 108). Adds the five A2UI invariants (RFC 0102), three localized-content invariants (RFC 0103), the voice voice-transcript-untrusted + graduated voice-bargein-no-partial-leak (RFC 0106), and self-hosted-endpoint-no-disclosure (RFC 0108), plus new threat-model-prompt-injection.md §4.8 / §4.9 / §4.10 sections (A2UI render surface, synthesized-audio boundary, live-audio ingress).
  • Conformance suite 1.25.0 → 1.37.0 — published independently on its own openwop-conformance/v* tags as each RFC's gated + always-on scenarios landed (A2UI, localized-content, approver-routing, speech-synthesis, the voice family, multi-party conversation, declarative pack kinds, self-hosted providers). Per the 2026-06 repo split, this corpus repo now publishes exactly this one artifact; the SDK ctx type additions (ctx.callTranscriber, the callSpeechSynthesizer streaming arm, the interrupt + A2A types) are sequenced into the openwop/openwop-sdks sibling repo.
  • Honest scoping carried on the public credibility surface. RFC 0106's optional voice arms (synthesis / turnDetection / bargeIn) and two of its §F invariants stay below full protocol-tier / dual-witness until their scenarios drive the adversarial leg + a second witness lands; RFC 0100's tasks/resubscribe stays a zero-witness optional arm pending a gated subtest; RFC 0108 closed single-witness (operator-private surface, no dishonest public claim). Each is documented as such in docs/KNOWN-LIMITS.md + INTEROP-MATRIX.md.

1.1.8

2026-06-14

the 2026-06 monorepo split + the RFC 0089–0100 graduation cycle

Ships the structural repo split and a dense two-week graduation cycle. The spec corpus now publishes exactly one artifact — @openwop/openwop-conformance — and everything else (SDKs, packs/registry, reference hosts/examples, the reference app, the CLI) moved to sibling repos. Twelve RFCs reached Accepted and two new ones (0099/0100) landed their normative floor. All wire shapes additive per COMPATIBILITY.md §2.1; no conformance pass invalidated.

  • Repository split (non-normative). The monorepo was decomposed with full history into sibling repos: SDKs → openwop/openwop-sdks (TS @openwop/openwop, Python openwop-client, Go), packs + published registry → openwop/openwop-registry, reference hosts + runnable examples → openwop/openwop-examples, the reference app → openwop/openwop-app (app.openwop.dev), and the CLI → openwop/openwop-cli. This repo now publishes one artifact: @openwop/openwop-conformance. ⚠️ Consumer-facing: the Go module path changed to github.com/openwop/openwop-sdks/go (re-pin required); npm + PyPI names unchanged. The pack-manifest schemas stay normative here. A coordinated v* spec release must be matched by SDK tags in openwop-sdks.
  • RFC 0099 — external-event trigger ingestion Accepted. Floor + graduation: the content-free TriggerEvent envelope (ctx.triggerData), the TriggerSubscriptionRegistration create contract served by POST /v1/trigger-subscriptions, the additive triggerBridge.ingestion capability sub-block, and SECURITY invariants trigger-ingestion-ssrf + trigger-ingestion-content-redaction. Graduated on dual live evidence vs suite 1.25.0 (reference openwop-app 00177-75t + non-steward MyndHyve 00271-cj5, both steward-curl-verified, trigger-ingestion.test.ts non-vacuous).
  • RFC 0100 — async / durable A2A tasks (Active; floor landed). New a2a capability block ({supported, agentCardUrl, streaming?, pushNotifications?, durableTasks?}) + the persisted A2ATaskState projection (taskId==runId, lowercase-hyphen state) + a2a-integration.md §"Async / durable Tasks" + SECURITY invariant a2a-push-egress-ssrf. Reference leg fully proven (openwop-app a2a-task-roundtrip 10/10 non-vacuous); stays Active pending a non-steward a2a witness.
  • RFCs 0096 / 0097 / 0098 — reviewable-learning proposals · standing goals · agent-platform portability Accepted. Floor + dual-live-evidence graduation: agents.proposals (inert proposal.schema.json), agents.goals (judge-based goal.schema.json), the top-level portability block (export-bundle.schema.json, refs-only, dryRun:true when import:true), 5 protocol-tier SECURITY invariants, 5 content-free events. openwop-app reference (00174-6m6, portability.import:true) + MyndHyve non-steward (00269-ljm, honest import:false opt-out).
  • RFC 0095 — connection packs Accepted. New kind:"connection" pack (portable signed provider definition the RFC 0045/0047 provider string resolves against) + connection-pack-manifest.schema.json + standalone spec/v1/connection-packs.md + the connection-pack-no-credential-material protocol-tier invariant. Dual evidence (MyndHyve 00268-x9l + openwop-app 00160-kjq).
  • RFCs 0093 / 0094 — protocol hardening + wire-shape reconciliation Accepted. 0093 pins four security/correctness gaps (webhook delivery-time egress re-validation + the protocol-tier webhook-cross-tenant-isolation invariant; signed interrupt-token lifecycle; retryable responses MUST NOT replay from the dedup cache; approval-gate timeout auto-rejects). 0094 repairs published-artifact defects (the unsatisfiable createRun schema → unevaluatedProperties:false + a satisfiability probe; cancelling status; vendor-event anyOf closure; single-sourced ai.message.chunk; completed InterruptPayload.kind union; capabilities.grpc + limits.maxRequestBodyBytes).
  • RFCs 0090 / 0091 / 0092 Accepted. 0090 — agent verifier turn (agent.verified event, verifier capability, multiAgent.executionModel.version: 6, verifier-no-content-leak); 0091 — multimodal perception input on ctx.callAI (content: string | ContentPart[], gated on aiProviders.input.modalities[]); 0092 — agent-level requiresCapabilities[] projecting to the RFC 0072 degraded[] field.
  • RFC 0089 — conformance certification bundle Accepted. The openwop-conformance --certify generator captures discovery (canonical-JSON SHA-256), derives claimedProfiles, records each scenario's terminal state, and never lists a vacuously-run scenario in results.passed; a real reference-host bundle is committed + round-trip-verified.
  • Conformance suite — @openwop/openwop-conformance 1.18.1 → 1.25.0 across the cycle (independently versioned, published on its own openwop-conformance/v* tags). Carries the RFC 0089–0100 scenario additions (trigger-ingestion, the async a2a-task-roundtrip subtests, proposal/goal/export-bundle, the five connection-pack scenarios, verifier-gating, callai-multimodal, agent-capability-degraded-projection, webhook-tenant-isolation, version-fold, stream-text-fixture, i18n-negotiation, grpc-transport).
  • SECURITY invariants — 114 → 125 total (99 protocol-tier). New across the cycle: verifier-no-content-leak, webhook-cross-tenant-isolation, connection-pack-no-credential-material, the five proposal/goal/export invariants, trigger-ingestion-ssrf, trigger-ingestion-content-redaction, a2a-push-egress-ssrf.
  • Reference app (now openwop/openwop-app). Before extraction, the app landed a governed agent-Workforce surface (seeded entity + deterministic synthetic history + metrics/governance/migration-wizard, all under /v1/host/sample/workforces, experimental x-host-openwop-workforce), the white-label foundation batch (deploy-posture gating, brand stamping, .env leak guards, an explicit-?tenantId=* fix that closed a latent cross-tenant GET /v1/agents visibility leak), and a board-rename + guided autonomy level. Operator surface only — no wire change.
  • Docs + editorial. Full post-split + post-graduation drift sweeps: status-label normalization onto the Stable / Stabilizing / Draft / Experimental legend, SECURITY/RFC count reconciliation, per-host conformance-evidence suite-version syncs, the INTEROP-MATRIX.md rewrite into a lean public matrix + the GOVERNANCE.md "Acceptance evidence tiers" taxonomy, and post-split path requalification across README/ROADMAP/PUBLISHING. interrupt.md documents suspend as the accepted alias of interrupt (additive, host SHOULD expose both).

1.1.7

2026-06-02

the Active→Accepted graduation program + agent-platform capstone + SDK parity

The largest graduation cycle since v1.0. The entire agent-platform program plus the bulk of the Active-RFC backlog graduated Active → Accepted on a non-steward host (MyndHyve), closing RFC counts to Accepted 85 / Active 1 / Draft 2. All wire shapes additive per COMPATIBILITY.md §2.1 — no schema, event, endpoint, or MUST change. This coordinated release also publishes the agent-platform read surface to the Python and Go SDKs (which had lagged the OpenAPI surface by 17 methods) and a TypeScript SDK minor. The header is 1.1.7 because 1.1.6 was consumed by an earlier TS-SDK-only npm patch. Full per-RFC history is in each RFC's Updated field, INTEROP-MATRIX.md, and the generated docs/PROTOCOL-STATUS.md.

  • Agent-platform program Accepted end-to-end (the capstone). RFC 0085 (openwop-agent-platform meta-profile) Accepted, certifying MyndHyve a _full_ agent platform; with 0077/0086/0087/0083 (live manifest dispatch / standing roster / org-chart / durable trigger-bridge) and 0081/0082 (eval suite / deployment lifecycle), the whole program is Accepted on a non-steward host.
  • Tool-egress, budget, and memory batches Accepted. RFCs 0078/0079 (portable tool catalog + credential-provenance/egress policy), 0084 (budget/quota/cost), and 0068/0080 (memory consolidation + capability reconciliation) graduated on green, non-vacuous gated behavioral scenarios.
  • Enterprise + provider + UX RFCs Accepted. 0050 (SAML/SCIM — a real XML-DSig ACS the steward drove over the live wire for all 7 §A variants incl. signature-wrapping, plus SCIM fail-closed), 0067 (provider-catalog auth-modes), and 0066 (x-openwop-form) + 0065 (outputRole) on genuine reference-frontend consumption.
  • Steward-side mechanism-codifications (amended criteria, architect-gated). 0069 (exec-class carve-out — RFC 0054 amendment precedent), 0080 (degraded-memory keystone), and 0042 (experimental capability tier) graduated steward-side, where a non-steward advertisement is structurally impossible or would be a false claim on the wire.
  • Core Standard Profile (0088) + capability document-root layout (0073) Accepted. 0088 freezes the black-box-proven stable Core floor (0029 prompt-resolution chain + 0059 workspace isolation graduated into the black-box set); 0073 makes the conformance suite _enforce_ the document-root capability layout.
  • Earlier-window graduations. RFC 0076 (pack runtime.requires[] + host ctx.http.safeFetch, §A + §B), 0072 (agent inventory + dispatch normative surface), 0054 (run diff & execution comparison), 0036 (multi-region idempotency + cross-engine ordering), 0025 (test-mode registry namespace), and 0056 (run feedback & annotations) all reached Accepted.
  • Real-isolation WASM sandbox reference host (RFC 0035). New examples/hosts/wasm-sandbox/ executes pack-loaded typeIds as WebAssembly; all 7 testable node-pack-sandbox-* SECURITY invariants graduated reference-impl → protocol. RFC 0035 itself stays Active (gated on non-steward adoption by a host that runs untrusted packs).
  • Conformance suite hardened + advanced to @openwop/openwop-conformance@1.18.1 (published per-package across this window): the agent-platform behavioral gates hardened against vacuous passes (1.17.0), the gated scenarios for every graduated RFC authored + published, the OTel collector-side BYOK-canary inspector + RFC 0041 §C replay-observable assertions landed, and the SAML behavioral leg expanded to the full 7-variant set.
  • External-audit remediation. The corpus gate now blocks on open high/critical external-audit findings; the 2026-05-31 re-review's seven-item bar was mapped to the gap-closure program; multiple stale docs/KNOWN-LIMITS.md rows were retired against landed evidence (multi-region simulation harness, cross-engine CF-8, sandbox-timeout, the open-RFC table).
  • New normative read surfaces (deferred Accepted-track). OpenAPI + AsyncAPI + TS SDK surface for the roster/org-chart (0086/0087) and eval/deployment (0081/0082) endpoints; AgentOrgChart Department/Role/Member published as named $defs.
  • SDK parity completed across all three reference SDKs + machine-enforced. A parity audit found those agent-platform read surfaces — plus RFC 0078 tool catalog, run diff, and prompt-template CRUD — had reached only the TypeScript SDK. Ported the 17 missing helpers to openwop-client (Python) and the Go SDK, and added tools.list/tools.get + getArtifact across all three. New sdk/parity-expectations.json declares a per-SDK status (typed / excluded) for every OpenAPI operation, and scripts/check-sdk-parity.mjs (openwop:check step 7) fails on any undeclared route or typed-surface regression. sdk/PARITY.md's stale "34/34/34 as of 2026-05-15" headline corrected to 44 typed / 4 excluded (the four packs-test write-mirror operations — a server-side conformance affordance, not a client surface). This release publishes that surface: openwop-client 1.1.7 (PyPI) + github.com/openwop/openwop/sdk/go v1.1.7 finally carry the 17 agent-platform methods, and @openwop/openwop 1.2.0 (npm — a minor, not a patch, because client.prompts.get now returns PromptTemplate | null to match the other get-by-id helpers + the Python/Go SDKs: a 404 resolves to the null sentinel while a 400 prompt_ref_ambiguous still throws, so callers can distinguish "not found" from "ambiguous"). The conformance suite stays at 1.18.1 (no new scenarios). The parity gate also gained a word-boundary per-method symbols check so it catches a single method being deleted from a shared path family.
  • Reference-app + host-sample. Agents tab + chat mention-symbol surface, durable read-through host-extension stores, cross-instance Kanban SSE fan-out, and a run of code-review / UX-review follow-ups. Rate-limit 429 envelope conformance (CF-6): the sample host now emits details.scope from the canonical closed enum ("tenant" | "route" | "global" | "key") per rest-endpoints.md §429 instead of internal limiter names (which moved to a non-normative details.reason), and honors OPENWOP_FORCE_RATE_LIMIT=true so the conformance harness can deterministically induce a 429 (rate-limit-envelope.test.ts).
  • Registry + packs. core.openwop.http@2.0.0 (RFC 0076 §B safe-fetch consumer) signed + published to the in-tree mirror; tarball-signature gate added + unsigned tarballs yanked; agent-pack source-schema $id bumps.
  • Release infrastructure + doc hygiene. Publish jobs are now idempotent — a corpus v* tag skips already-published package versions rather than partial-failing (PUBLISHING.md documents the property). generate-protocol-status.mjs --write self-heals the README corpus counts, retiring the giant-status-line merge bottleneck. The [1.1.6 — unreleased] changelog block was collapsed to release-notes shape + renamed 1.1.7 (1.1.6 was consumed by the TS SDK patch); the committed public/ site was rebuilt + deployed to openwop.dev to surface the graduations; and the INTEROP-MATRIX.md pass-rate header was corrected to mark 1.15.0 as the _last_ re-measurement (the published suite has since advanced to 1.18.1; a full re-measurement is pending).
  • OpenAPI agent-path disambiguation (audit response). /v1/agents/{agentId} and /v1/agents/{agentId}/deployments now constrain agentId with pattern: '^(?!roster$|org-chart$).+$', so it can never collide with the sibling collection routes /v1/agents/roster/{rosterId} + /v1/agents/org-chart/{departmentId} (the path the third-party auditor flagged: /v1/agents/roster/deployments was structurally two-way). redocly's purely-structural no-ambiguous-paths rule (which can't see the pattern) is scoped off for exactly those two paths in api/.redocly.lint-ignore.yaml with a documented rationale — the rule stays enabled globally. Non-breaking: roster/org-chart were already reserved/ambiguous as literal agentIds.
  • README published-artifact versions corrected. The "v1.x published artifacts" line pinned all four packages at v1.1.0; updated to the actual published versions — @openwop/openwop 1.1.6, @openwop/openwop-conformance 1.18.1, openwop-client (PyPI) 1.1.5, Go modules v1.1.5 — and clarified that the three SDKs hold feature parity even though patch versions diverge (the TS SDK took a 1.1.6 patch for the agent-platform helper surface) and the conformance suite versions independently.
  • CLI agent-platform surfaces (@openwop/cli 0.1.2 → 0.2.0). The CLI now drives every demo-app protocol surface it previously lacked: \roster\ (RFC 0086), \org-chart\ (RFC 0087), \kanban\ boards + cards (with an SSE \watch\), \orgs\ orgs/teams/groups/roles/members RBAC + effective-access (RFC 0049), \workspace\ files (RFC 0059 §C real CRUD; the cross-owner test seam is deliberately not exposed), \byok\ secret refs (values never returned), and user-defined-agent \create\/\update\/\delete\ on the \agents\ group. All read commands support \--json\; all destructive commands require \--yes\. Also fixed the stale \--version\ constant (reported 0.1.0 on the 0.1.x package) and the pre-existing \agents run\ flag-parsing bug where \--task-json\/\--no-validate\ never took effect (option keys are camelCased).

1.1.5

2026-05-28

OpenWOP CLI launch + chat-channel parity + agent-runtime arc Accepted

Ships the first public release of the OpenWOP CLI to npm, brings channel messaging (Signal · iMessage · WhatsApp · Discord) to feature parity with the in-app AI chat, and closes the agent-manifest-runtime arc end-to-end on a non-steward host. All wire shapes additive per COMPATIBILITY.md §2.1.

  • OpenWOP CLI v0.1.0 ships to npm. npm install -g @openwop/cli — a control-plane CLI for any OpenWOP-compatible host (auth onboarding, capabilities, runs + SSE streaming, prompts · memory · agents · interrupts, channel-relay daemons). Operator-side, independently versioned on its own cli/v* SemVer line; published through the existing OIDC publish pipeline with provenance (a new publish-cli job mirrors publish-ts-client). The 4085-line TypeScript bundle runs under strict + noImplicitAny: true; 149 node --test cases gate every release. A new /cli route in the demo app surfaces install + command catalog; a new homepage card on openwop.dev (§01.5 "Try it from your terminal") links the public to it.
  • TypeScript + Python + Go SDKs bump 1.1.4 → 1.1.5 in lockstep. Conformance suite moved 1.7.0 → 1.10.0 mid-cycle across 12 net-new scenario files (artifact-type-pack-install, artifact-schema-compile-bounded, chat-card-pack-{execution,manifest-validation}, agent-manifest-runtime, byok-auth-modes, commitment-fired, exec-not-protocol-tier, memory-consolidation-{shape,idempotent}, artifact-type-store-without-render). No wire-shape changes.
  • Messaging → AI-chat parity end-to-end. The relay-gateway lands routing enforcement (channel + peer → workflow OR agent), conversation-history threading (OPENWOP_MESSAGING_HISTORY_LIMIT-bounded turn replay), policy + pairing + allowlist + requireMention enforcement, direct agent dispatch as a routing target (with OPENWOP_MESSAGING_AGENT_DISPATCH_TIMEOUT_MS abort guard), and cross-channel identity unification. A code-review hardening sweep added tenant-scoped pairing/allowlist routes, CSPRNG pairing codes, a requireMention deny-all tripwire, defense-in-depth tenant scoping on listMessagingTurns, and a ?limit clamp on the delivery-log route. Postgres migrations 9-13 land on live Cloud SQL via the deploy ladder.
  • RFC 0070 + 0072 + 0074 agent-runtime arc closes end-to-end. 0070 (agent-manifest runtime — agents.manifestRuntime) graduated Active → Accepted on MyndHyve production rev workflow-runtime-00394-jun (inventory + dispatch seam wired with toolSurface allowlist filter + attributed agent.reasoned/agent.decided). 0072 (normative GET /v1/agents) shipped. 0074 (tenant-scoped manifest-agent inventory — additive installScope: 'host' | 'tenant') graduated on multi-tenant rev workflow-runtime-00398-vup with steward-curl-verified A→1 agent, B→empty + cross-tenant 404. The 31 agent manifests on the workflow-engine host become runnable via agents.manifestRuntime + AgentRegistry + loader + dispatch.
  • RFC 0071 (artifact-type packs + chat card packs) Accepted overall. Phase 1 graduated on MyndHyve Phase-1 adoption (16 → 7 reconciled types served as validated: true, validation: "open", registrationSource: "host"). Phase 2 graduated on Slice-B rev workflow-runtime-00402-bey — the real core.chat.cardExecutectx.aiEnvelope.generate binding with host.aiEnvelope advertised + contentTrust: "untrusted" on card-input-derived prompt segments. artifact-type-packs.md + chat-card-packs.md promoted DRAFT → FINAL. Two new SECURITY invariants: artifact-schema-compile-bounded + chat-card-input-trust-boundary.
  • RFC 0073 (capability families are document-root properties of /.well-known/openwop) reconciles a contradictory discovery layout. The schema rooted agents / secrets / etc. at the doc root (no capabilities wrapper), but ~40 conformance scenarios + 4 reference hosts read nested — no host satisfied both. RFC 0073 lands a normative MUST + root-first conformance accessor; Phases 1-3 migrate 78/78 capability-gated scenarios; all 4 reference hosts (in-memory, sqlite, postgres, python) emit root + a deprecated-mirror via spread. Phase 4 (drop the wrapper-fallback + nested mirrors) defers to a future minor.
  • RFC promotions Draft → Active. 0054 (run-diff — GET /v1/runs/{runId}:diff endpoint + canonical-comparison exclusion list pinned). 0073 (capability-root layout). 0074 (tenant-scoped agent inventory, amends 0072). 0075 (artifact-type-packs real-world adoption amendment, amends 0071 Phase 1 — host-native registration tier, additionalProperties: false MUST → SHOULD, per-type capability facets). Plus RFC 0071 Phase 2 (chat card packs) before its same-cycle promotion to Accepted.
  • NEW Draft RFCs filed. 0067 (provider-catalog conventions — additive aiProviders.authModes + provider-name vocabulary). 0068 (memory consolidation + standing commitments — agents.memoryConsolidation / agents.commitments + content-free agent.memory.consolidated / commitment.fired events). 0069 (host-extension safety contract for exec-class tools — normative MUST-NOT carving exec out of protocol-tier + the exec-must-not-be-protocol-tier invariant). All additive.
  • Registry hardening. Three published core.openwop.agents.* packs republished as @1.0.1 with their systemPromptRef bodies now bundled — the bundler dropped prompts/ from the tarball at @1.0.0 and an RFC 0003 §C fail-loud host (MyndHyve) correctly rejected them. New scripts/check-pack-prompt-refs.mjs (wired into openwop:check step 7) fails CI at build time if any source pack's systemPromptRef doesn't resolve to a bundlable prompts/ file. Separately: 81 pack-version index.json files corrected to declare the real publisher signing keys (openwop-team-1, myndhyve-internal-1, community-demo) instead of the stale openwop-registry-root default; check-registry-signer-consistency guard added.
  • Reference workflow-engine: /readiness reports managed-provider health. When providers.json advertises a managed: true tier (e.g., openwop-free) but its server-held key was never seeded, /readiness returns 503 { status: "degraded", checks: { managedProviders: [...] } } with a per-provider detail naming the env var to set. Catches a dropped/unmounted-secret class of outage at deploy time instead of waiting for the first failed run.
  • Honest deferrals + corrections. RFC 0054 (run-diff) stays Active — graduation to Accepted awaits a non-steward host advertising the endpoint. RFC 0072 graduation awaits a second cross-language host. The RFC 0073 wrapper-fallback removal (Phase 4) defers to a future minor. The v1.1.4 tag from 2026-05-26 silently did not trigger the publish workflow (no run record); this v1.1.5 corpus-aligned tag catches the TS SDK / Python / Go / conformance up. DEPLOY.md §6 corrected — the from-scratch --env-vars-file + --set-secrets form was a footgun for live redeploys; safe-redeploy recipe documented separately.
  • Site updates. Homepage §01.5 "Try it from your terminal" card on openwop.dev introduces @openwop/cli with a dark-bg install snippet + capabilities list + deep-link to the in-app /cli command catalog. PUBLISHING.md artifact table gains the CLI row noting independent SemVer + cli/v* tag pattern.

1.1.4

2026-05-26

MyndHyve cohort live + autonomous-agent-runtime cohort + 19 RFC graduations

Closes the first full week of post-1.1.3 cross-host adoption: the 8-RFC MyndHyve protocol-extension cohort + the 5-RFC autonomous-agent-runtime cohort all reach Accepted on production non-steward implementations, the multi-agent execution-model version: 1-4 ladder closes end-to-end, and three rounds of MyndHyve advertisement land RFCs 0028 / 0029 / 0040 / 0041 / 0055 / 0057. All wire shapes additive per COMPATIBILITY.md §2.1.

  • TypeScript SDK 1.1.4 (@openwop/openwop) publishes the RFC 0057 memory.written typed event helper + the round-2 SDK-migration finishers (runsClient / interruptsClient / promptsClient / streamsClient on the published SDK, debug-bundle reverted to SDK after 1.1.3's regression). Python (openwop-client) and Go (openwopclient) bump in lockstep. No wire-shape changes.
  • Conformance suite @openwop/openwop-conformance 1.5.0 → 1.6.0 → 1.6.1 → 1.7.0. 1.6.0 ships the 28 RFC 0045–0054 cohort scenarios; 1.6.1 patches a stale secrets.scopes allowlist in redaction.test.ts; 1.7.0 lands the autonomous-agent-runtime cohort coverage (+1 net-new scenario file plus per-RFC behavioral additions). Bundled synthetic SAML IdP fixture lands closing the RFC 0050 deferred conformance gap.
  • MyndHyve protocol-extension cohort live in production (8 RFCs Draft → Accepted in one day). RFCs 0045 (connector pack manifest), 0046 (host.credentials), 0047 (host.oauth), 0048 (tenant·workspace·principal identity model), 0049 (RBAC scopes + authorization.decided), 0051 (approval & deployment-gate primitive), 0052 (scheduling & time-based triggers), 0053 (dead-letter routing) all graduated on MyndHyve workflow-runtime revision 00211-69w against @openwop/openwop-conformance@1.6.028 PASS / 0 FAIL (commit 85275cdf on the MyndHyve side). RFC 0050 (SAML/SCIM) + 0054 (run-diff) stay Draft per documented MyndHyve opt-outs.
  • MyndHyve round-3 graduations (3 RFCs Active → Accepted 2026-05-26). Revision workflow-runtime-00217-q7c advertises capabilities.prompts.agentBindings: true (RFC 0029), aiProviders.maxInlineMediaBytes: 10485760 + modelCapabilities.advertised: ['vision-input', 'image-output'] (RFC 0055), and capabilities.memory.attribution.{supported: true, emitsWriteEvents: true} with canonical memory.written event dual-emitted alongside vendor x-host-myndhyve-memory-written (RFC 0057). Curl-verified on https://workflow-runtime-gjw5bcse7a-uc.a.run.app/.well-known/openwop.
  • Multi-agent execution-model roadmap CLOSED end-to-end on a non-steward host. RFC 0040 (Phase 3 cross-host causation) + RFC 0041 (Phase 4 replay determinism) graduated Active → Accepted on MyndHyve's multiAgent.executionModel.version: 4 advertisement + replayDeterminism.{supported: true, llmCacheKeyRecipe: "spec-rfc-0041", refusalDivergenceEmission: true} block. The version: 1-4 ladder is fully production-validated; version: 5 (RFC 0061) opens the autonomous-agent-runtime extension.
  • Autonomous-agent-runtime cohort (7 RFCs filed + 5 graduated). RFCs 0058 (run-execution bounds), 0059 (agent workspace), 0060 (host.heartbeat), 0061 (stateful agent-loop lifecycle, executionModel.version: 5), 0062 (memory.distillation "dreams"), 0063 (core.subWorkflow.outputAttestation), 0064 (host.toolHooks) filed Draft with Phase-0 architect-decision-batch clearance. Within the cycle: 0059/0060/0062/0063/0064 graduated Draft → Active → Accepted via in-memory reference-host M2 enforcement (each ships the documented POST /v1/host/sample/* seam + all scenarios green); 0058 + 0061 graduated to Active pending second-host M2 enforcement.
  • RFC 0028 Tier-2 post-promotion strengthening. Workspace-membership normative + canonical workspace_membership_required 403 envelope error code + 2 new protocol-tier SECURITY invariants (prompt-mutation-workspace-membership-enforced, prompt-read-workspace-membership-enforced), filed in response to a self-disclosed adopter Admin-SDK-bypasses-DB-rules vulnerability — workspace gating now uniformly enforced across write + read paths.
  • Reference-host milestones across all four hosts. in-memory: 5 M2 host surfaces (RFC 0059/0060/0062/0063/0064). Postgres: RFC 0026 cost-attribution + RFC 0031 model-capability gate + RFC 0040 Phase 3 cross-host causation + RFC 0056 feedback + RFC 0057 memory.written + RFC 0058 runTimeoutMs enforcement. SQLite: ports RFC 0022 dispatch/subWorkflow + 0026 + 0031 + 0056 + 0057 + 0058 from Postgres. Reference workflow-engine advertises capabilities.memory.attribution.emitsWriteEvents: true + emits on run-summary write. RFC 0031 model-capability gate-decision test seam lands on Postgres flipping the synthetic assertions live.
  • Reference-app — 6 plan items closed end-to-end. Items #12 (pre-flight workflow validation against advertised engine limits), #13 (dedicated audit-log viewer page at /runs/:runId/audit), #15 (multi-turn conversation panel consuming conversation.{opened,exchanged,closed} events), #16 (A2A peer placeholder, forward-compat), #20 (Publish-to-registry helper banner), #25 (sticky-note canvas annotations via new clientOnly: true NodeCatalogEntry flag). The app-buildable plan closes at 23 ✅ / 2 🟡 / 0 ❌; the two 🟡 items are structurally unblockable from the openwop side and tracked in docs/myndhyve-round-2-handoff.md. Multimodal renderer (RFC 0055 §C consumer), media-emitting demo node, persistent HITL artifact cards, notification-system rewrite (Web Push + OS notifications + preferences + quiet hours + flagged review queue) all ship in the same cycle.
  • vendor.myndhyve.brand@1.1.0 — first non-steward x-openwop-form pack (PR #232). Pilot annotation on brand.persona.discover.config.json adds provider-picker + model-picker { dependsOn: ["provider"] } (RFC 0066 normative cascade-clear). Path-to-Accepted for RFC 0066 unlocks once MyndHyve re-pins the new manifestHash. Bonus: scripts/emit-pin-json.cjs emits the 7-hash pin-block JSON for any registered pack version.
  • +8 protocol-tier SECURITY invariants (94 → 102 total; 68 → 70 protocol-tier graduated to gate-verified). New: authorization-fail-closed (RFC 0049), prompt-{mutation,read}-workspace-membership-enforced (RFC 0028 Tier-2 — paired with a self-disclosed adopter vulnerability close-out), media-asset-url-tenant-scoped (RFC 0055), memory-attribution-{no-content,tenant-scoped} (RFC 0057), workspace-cross-tenant-isolation (RFC 0059 M2), subrun-merge-approval-fail-closed (RFC 0063 M2). Every protocol-tier MUST-NOT has at least one public test in conformance/src/scenarios/.
  • NEW Draft RFCs filed (12). 0050 (SAML/SCIM enterprise identity profiles), 0054 (run-diff & execution comparison), 0055 (multimodal envelope variants), 0056 (run feedback & annotations), 0057 (memory write-attribution), 0058–0064 autonomous-agent-runtime cohort (run-execution bounds / agent workspace / heartbeat / stateful agent-loop / memory.distillation / subWorkflow attestation / host.toolHooks), 0065 (workflow node primary-output annotation — advisory outputRole: "primary" | "secondary" for chat-surface deterministic-artifact picking), 0066 (x-openwop-form vendor extension on pack configSchema).
  • Honest non-graduations + opt-outs. RFC 0058 wall-clock arm took two rounds — MyndHyve initially advertised maxNodeExecutions in error, honestly retracted via docs/openwop-adoption/rfc-0058-round-3-retraction.md, then landed the real arm on 2026-05-26 (limits.maxRunDurationMs: 600000 now advertised; maxLoopIterations: null honestly absent — host attests run-create clamping + canonical cap.breached { kind: 'run-duration' } emission). RFC 0058 stays Active pending second-host adoption. RFC 0035 (sandbox), 0036 (multi-region), 0050 (SAML), 0054 (run-diff) opt-outs documented at docs/openwop-adoption/round-3-closure-2026-05-26.md with re-evaluation criteria. RFC 0058 round-3 closure agreement loop documented at docs/openwop-adoption/rfc-0058-round-3-retraction.md.
  • Multi-agent "Phase N" → version-tagged rename + site regenerated. External-facing prose drops internal "Phase N" labels for the multi-agent execution model in favor of the wire-shape version: N identifier (per the 2026-05-24 external-reader feedback). Site openwop.dev/spec/v1/* regenerated to pick up RFC 0045–0057 + cohort promotions + 404 page (PR #164). Reference-app SDK migration finished — runsClient / interruptsClient / promptsClient / streamsClient all on the published SDK (cookie-mode SSE stays on native EventSource pending an SDK credentials: 'include' hook).

1.1.3

2026-05-23

coordinated SDK release for first cross-host adoption

Closes the workflow-engine reference-host pass-rate inflation that the 2026-05-22 external standards-readiness review flagged, lands first non-steward host adoption of four RFCs, and ships the Phase 4 behavioral harness end-to-end. All wire shapes additive per COMPATIBILITY.md §2.1.

  • TypeScript SDK 1.1.3 (@openwop/openwop) publishes coordinated parseRefusal() + buildReasoningDirective() helpers. Python (openwop-client) and Go (openwopclient) bump in lockstep. No wire-shape changes.
  • Workflow-engine reference host pass-rate 80.9% → 95.5% via two bundled-path bugfixes (envelopeAcceptor.ts schema lookup + promptStore.ts/promptCompose.ts fixtures lookup) — both cases of __dirname + '..' × N overshooting under the esbuild-bundled tree. New shared _repoPath.ts::locateRepoDir() helper + 5-test regression guard. The inflated 129-failure number was a cascade from a single ENOENT crash, not 129 real conformance gaps.
  • RFC 0041 §B Phase 4 closes — replay-divergence-at-refusal executor wiring lands the last it.todo from the 5-track audit harness. The workflow-engine's :fork mode: replay path now emits replay.divergedAtRefusal and fails with error.code: 'replay_diverged_at_refusal' when an envelope kind diverges between source and replay (both directions). Gated on OPENWOP_MULTI_AGENT_EXECUTION_MODEL_PHASE_4=true. RFC 0041 path-to-Accepted opens (gate: second host advertising multiAgent.executionModel.version: 4).
  • Phase 4 behavioral harness — Tracks 1/2/5/6/7 + RFC 0042 close. Three new HTTP test-seam endpoint families on the reference workflow-engine drive five new conformance scenarios: multi-region partition simulator, cross-engine append-ordering harness, sandbox MVP (7-of-8 RFC 0035 §B invariants), secret-leakage OTel-attribute coverage, RFC 0042 experimental-tier shape probe. Suite scenario count 205 → 210. NEW spec/v1/host-sample-test-seams.md §6–§8 documents the new seams normatively.
  • First non-steward cross-host adoption. MyndHyve (api.myndhyve.ai) ships Tier-1 advertisements for RFC 0021 (envelope), RFC 0027 (prompt templates with observability: 'full'), RFC 0028 (read-only prompt library), RFC 0029 (override hierarchy, node layer), RFC 0034 (OTel test seam, empty-buffer Tier-1), RFC 0039 Half B (memory lifecycle MAE-2 + MAE-3, crossChildMemoryConcurrency: 'strict'), RFC 0040 Sub-5b (MCP API-key auth). Verified live against /.well-known/openwop.
  • RFC promotions Active → Accepted (5 total this release): 0027 (prompt templates) — first non-steward prompts.supported: true + observability: 'full'; 0034 (OTel collector test seam) — first non-steward Tier-1 seam-shape adoption; 0037 Phase 1 (multi-agent execution model) — first vendor-neutral validation signal; 0039 Half A (multi-agent confidence + memory lifecycle) — cross-host evidence via MyndHyve commit c4342b5b against suite v1.5.0; 0044 (confidence-escalation interrupt-kind advertisement, clarification to RFC 0039 §A).
  • NEW Draft RFCs. 0042 (experimental capability tier — tier ∈ {stable, experimental} + experimentalUntil ≤ 12-month sunset + derived openwop-experimental profile + conformance soft-skip routing under default mode). 0043 (registry + extension policy + IPR posture — consolidates DCO + Apache-2.0 + CC-BY-4.0 + namespace reservation rules).
  • Vendor-namespace pattern locked. MyndHyve picked Option 1 (x-host-myndhyve-memory-written) per host-extensions.md §"Canonical prefixes" for host-private SR-1 audit events. Preserves wire-shape compat with strict RunEventType validators; the canonicalize-via-RFC path stays open for any second host that wants the same shape.
  • Honest correction — registerHostSampleRoutes wire-up bug. MyndHyve's /v1/host/sample/* routes were deployed for days but never wired into the runtime (404 in production until commit 60b569de). Four seams affected (RFC 0027 §E compose, RFC 0041 §A cache-key, RFC 0021 envelope-accept, RFC 0034 OTel scrape). All four now exercisable end-to-end; RFC 0027 status stays Accepted (the advertisement was real; the bug was wire-up, not logic).
  • Audit response artifacts. NEW docs/AUDIT-RESPONSE-2026-05.md (point-by-point reply to the 2026-05-22 external review with calendar tripwires). NEW docs/CONFORMANCE-RUNS-2026-05.md (re-measurement of all 4 reference hosts against @openwop/openwop-conformance@1.4.0 + per-failure taxonomy). NEW docs/PHASE-4-PROGRESS.md (Phase 4 close-out accountability with closing-commit citations).
  • Conformance suite 1.4.0 → 1.5.0. RFC 0044 vendor-kind routing relaxation splits one strict-equality assertion into discrete it() blocks (+6 tests, +6 passes). Postgres 1473/1564 (94.2%), SQLite 1486/1564 (95.0%), in-memory 1445/1564 (92.4%), Python 1387/1564 (88.7% total / 100% of applicable).
  • Reference workflow-engine + sample-app polish. Real-LLM default in the builder (vendor.openwop-sample.chat-responder replaces the deterministic mock-ai node + managed openwop-free credential tile by default); Copy/Export buttons on the event-stream view; Cloud Run deploy-plumbing close-out (vendored schemas/ + dual-mount conformance-fixtures/ so the bundled host resolves sibling-repo paths under /app/lib); .gitignore for harness runtime state (.db-shm/.db-wal, .byok-master-key, host-fs/).
  • Site shipped at openwop.dev (2026-05-21). 13 new content pages + REST API explorer (Redoc) + AsyncAPI + gRPC transport explorers + JSON-LD TechArticle structured data on every spec doc. Star-on-GitHub CTA in the marketing footer.

1.1.2

2026-05-21

gap-closure batch + envelope-hardening track + ecosystem launches

The first patch release after v1.1.1 closes every gap from the 2026-05-19 → 2026-05-21 batch covering the envelope LLM-contract-hardening RFCs, the prompt-library track, the dispatch primitives, the multi-agent execution model, the agent-pack catalog, and the marketing-site launch at openwop.dev. All wire shapes additive per COMPATIBILITY.md §2.1.

  • TypeScript SDK 1.1.2 (@openwop/openwop), Python (openwop-client), Go (openwopclient) all bump in lockstep. Conformance suite @openwop/openwop-conformance 1.1.1 → 1.4.0 over the release window (1.2.0 / 1.3.0 / 1.4.0 minor bumps for new behavioral scenario families).
  • Marketing site shipped to openwop.dev (2026-05-21). First public surface for the protocol. Multi-page spec corpus rendered from spec/v1/*.md, demo card, Star-on-GitHub CTA. Companion app.openwop.dev workflow-engine sample app deployed in parallel.
  • spec/v1/ai-envelope.md DRAFT → FINAL v1.1 (2026-05-18). Closes the AI Envelope specification gap that was the largest remaining v1.0-era hole. Normative for envelope-acceptor wire shape, refusal kinds, capability stacking, and SR-1 secret redaction.
  • Envelope-hardening track (RFCs 0030–0033) filed + promoted Draft → Active → Accepted in 4 days (2026-05-20 → 2026-05-21). 0030 envelope reasoning field + Tier-1 structured-output subset. 0031 envelope variant discrimination + model-capability declarations. 0032 envelope-reliability run-event vocabulary. 0033 envelope-completion contract (truncation vs schema-violation retry routing). Reference-host emission landed in dispatchStructured(); conformance scenarios cover all four RFCs end-to-end.
  • Prompt-library track (RFCs 0027 / 0028 / 0029) filed Draft + promoted Active (2026-05-19 → 2026-05-20). RFC 0027 (prompt templates) reference-host implementation + Phase A wire shape + 4-kind dispatch wiring + slotIndex correctness. RFC 0028 (prompt library endpoints) reference-host /v1/prompts* endpoints + PromptStore + example prompt pack. RFC 0029 (prompt override hierarchy) four-layer resolver + /v1/host/sample/prompt/resolve seam. RFC 0027 §F shared divergencePoint schema diff.
  • Multi-agent track filed Draft. RFC 0035 (sandbox execution contract), RFC 0036 (multi-region + cross-engine), RFC 0037 (multi-agent execution model Phase 1 — first vendor-neutral validation tripwire), RFC 0039 (multi-agent Phase 2 confidence-floor escalation + memory lifecycle MAE-2/MAE-3), RFC 0040 (Phase 3 cross-host causation), RFC 0041 (Phase 4 replay determinism under nondeterministic models). RFC 0037 Phase 1 promoted Draft → Active same day with reference-host wiring + behavioral conformance.
  • RFC 0034 (OTel collector test seam) filed Draft → Active (2026-05-21). Replaces the failed POST-based shape with a GET-based scrape after the standards-readiness review surfaced the POST→GET reconciliation gap.
  • RFC promotion cohort Active → Accepted (15 RFCs): 0013 (workflow-chain packs — Draft → Active → Accepted same day on Phase 4 in-tree example landing); 0014–0021 graduation cohort (8 capability RFCs — behavioral conformance via opt-in test seam); 0022 (core.dispatch + core.subWorkflow runtime variable mapping — Postgres reference impl + dispatch trio); 0023 (conformance agent-event emitters); 0024 (streaming agent.reasoned deltas + SDK typed-helper rollout); 0026 (provider.usage event — filed Draft → Active → Accepted same day); 0030 / 0031 / 0032 / 0033 envelope-hardening track promoted Active → Accepted at the close of the release window.
  • 5 new Draft RFCs filed against the 2026-05-21 standards-readiness review findings. Each maps to a specific audit finding; full close-out lands in 1.1.3's Phase 4 harness work.
  • Agent pack catalog (4 tiers, 28 packs total). Phase 1 — Tier 0/1 foundations (9 packs). Phase 2 — Tier 2 productivity skills (5 packs). Phase 3 — Tier 3 vertical agents (10 packs). Phase 4 — Tier 4 crews + skills-bridge (4 packs). Catalog seeded with reference manifests + signing material for downstream registry publication.
  • **17 core.openwop.* packs published to packs.openwop.dev** under steward-internal pre-audit (2026-05-17). First non-trivial registry population. Includes pre-publication triage finding: core.openwop.http@1.1.2 (idempotency-key generator made deterministic), core.openwop.data@1.2.1 + core.openwop.crypto@1.0.2 (correctness fix for nodes mis-declared as pure), core.openwop.ai@1.1.1 + core.openwop.crypto@1.0.3 (defensive parsing of model output + JWT shapes), core.openwop.ai@1.1.2 + core.openwop.mcp@1.1.1 (UNTRUSTED-marker discipline on ctx.trustBoundary='untrusted' runs), core.openwop.agents@1.0.1 (raw-JS tool handler — closes OPENWOP-AUDIT-2026-003). Old core.openwop.ai@1.1.1 + core.openwop.mcp@1.1.0 marked deprecated.
  • Pack patches: SSRF + JWT alg-confusion fixes (P0.1) (2026-05-17). Yank-and-republish on the affected versions.
  • Workflow-chain packs (RFC 0013) — Phases 1–4 land in sequence. Reference example at examples/branching-workflow/. Phase 4 in-tree example demonstrates chain-pack composition end-to-end.
  • apps/workflow-engine@P3 — Firebase Auth signup + Cloud SQL persistence + KMS-encrypted BYOK (2026-05-17). Production rollout fixes (post-mortem) (2026-05-18). First fully-managed reference deployment serving as the app.openwop.dev surface.
  • Workflow-engine sample app — 30+ feature commits covering: BYOK canary echo node + provisioning, core.channelWrite + append-with-TTL reducer, capability_not_provided refusal contract, idempotency body-hash mismatch, quorum-aware approval gate, recursionLimit + conversationPrimitive refusal, core.subWorkflow executor + variable mutation seam, JSON content negotiation, getWorkflow endpoint + strict streamMode validation, bulk-cancel endpoint + idempotency replay header, MCP discovery shape + approval resume validation, fixture input-port → variable resolution, credential-shape redaction, cache hit semantics + debug-bundle endpoint, fs absolute-path rejection + kv.cas canonical shape, events/poll lastSequence + SSE bufferMs aggregation, prompt-library UI staging, managed "Try it free" provider tile, RFC 0022 dispatch cluster, parent/child cancel-cascade interrupt profile, external-event interrupt support, AI chat viewport-lock + Lucide thumbs icons.
  • Storage adapter parity harness — SQLite vs Postgres via pg-mem (2026-05-18) + real Postgres via @testcontainers/postgresql for end-to-end behavioral fidelity. Closes the storage-adapter parity gap from the v1.1.0 close-out.
  • Conformance close-outs: 7 aiEnvelope. scenarios graduated from shape probes to behavioral assertions (2026-05-18); agent.toolReturned causationId pairing tightened; envelope-track it.todo placeholders drained (sub-tracks E + E2 + A.reasoning-redaction); OPENWOP_REQUIRE_BEHAVIOR wired across the prompt-\ scenario family; soak-gate close-out (opt-out axes + SQLite artifact stub).
  • Untrusted-content propagation, persisted envelope-correlation dedup, downstream-LLM untrusted-content wrap, envelope-contract capability stacking refusal, approval-gate trust-boundary refusal — five protocol-tier behavioral hardening rows close in the sample-host (2026-05-19).

1.1.1

2026-05-15

post-1.1.0 additive cleanup + RFC 0012

Six additive commits landed on main after the v1.1.0 release tag. None changes a wire shape; all ship in a 1.1.1 patch when the registry SDKs are next published. Two close adopter-experience footnotes (lockfile demo + community pack re-sign), one closes a conformance-probe scope limit (MCP transports), and three close the RFC-process self-acceptance loop (0008 promotion + node-packs §WASM cross-link, 0001 promotion + CHANGELOG status drift fix).

  • Workspace lockfile demo (daeaef5) — examples/core-packs-lockfile/openwop-pack-lockfile.json + README pins the 4 audit-gated core packs (core.openwop.{ai,http,mcp,triggers}@1.0.0) using the pack-lockfile schema. Demonstrates SRI integrity + Ed25519 signature material for offline / air-gapped resolution. Closes the controllable half of the "build + sign + lockfile in-tree" Phase E task; the audit-blocked half (publication to packs.openwop.dev) remains gated on SECURITY/external-audit-engagement.md §2.1.
  • community.openwop-team.demo re-signed (0bf08cc) — Option-B reconciliation of a 3-way signing-identity drift. The demo pack now ships signed by community-openwop-team-demo-1 (over canonical pack.json) instead of openwop-registry-root (over tarball), matching PACKS-MVP-PLAN.md §211's per-tier-key intent and illustrating the per-publisher-identity pattern. New registry/keys/community-openwop-team-demo-1.pub + signingKeys[] entry in registry/.well-known/openwop-registry.json (namespace-scoped to community.openwop-team.demo only — cannot sign for core. or vendor.). Canonical verifier (registry/scripts/verify-signatures.mjs) passes 29/29.
  • MCP probe scope-limit footnote closed (beb5ae6) — all three MCP transports now verified end-to-end against @modelcontextprotocol/sdk@1.29.0. SSE-streamed responses verified via the same SDK without enableJsonResponse (probe's existing readSseUntilId correlates frames by JSON-RPC id). Stdio transport — HTTP-incompatible by design — exercised via the new examples/mcp-stdio-bridge/ shim that wraps any newline-delimited-JSON-RPC stdio server as HTTP for the probe (bundled echo-stdio-server.mjs + per-session-id child-process lifecycle; 2/2 pass). INTEROP-MATRIX.md §"Composition partners" MCP row + spec/v1/mcp-integration.md §"Conformance + interop" + docs/PROTOCOL-GAP-CLOSURE-PLAN.md Track 6 all updated to retire the previous scope-limit language.
  • RFC 0008 (WASM ABI) promoted Active → Accepted (6118cce, 2026-05-13) — all 8 acceptance-criteria items satisfied. The one previously-stuck gap (spec/v1/node-packs.md §"WASM runtime" cross-link) landed in the same commit with a 6-scenario coverage table mapping each wasm-pack-*.test.ts to its RFC 0008 anchor. The previously-stale Open spec gaps row NP1 — WASM ABI for language: wasm packs flipped to ✅ closed. README + CHANGELOG status banners refreshed to reflect 0008/0009/0010/0011 all Accepted.
  • RFC 0001 (RFC process) promoted Active → Accepted (20e0d1c, 2026-05-13) — closes the meta-RFC's self-acceptance loop. All 6 acceptance-criteria items confirmed: RFCS/README.md + 0000-template.md + this file shipped together; GOVERNANCE.md cross-references RFCS/ at five locations; CHANGELOG.md records the RFC process landing; rfc PR label created in the public repo (#5319e7 purple, description references the process RFC). Subsequent normative additions land under standard RFC review rather than the bootstrap waiver pattern. Same commit fixed CHANGELOG status drift for RFCs 0009/0010/0011 (had been stale at Active even though all three were promoted to Accepted 2026-05-12).
  • Final RFC ladder state (2026-05-13): RFCs 0001–0011 all Accepted (11 total). 0000 is the template scaffold; 0012 (memory compaction) is parallel-session Draft. Every RFC with a satisfied acceptance checklist is now promoted.
  • RFC 0012 (Memory Compaction Profile) Active → Accepted (2026-05-15) — comment window waived per CONTRIBUTING.md §"Bootstrap-phase notes" (sole-steward repo, no non-steward maintainer of record, no external commenters during the 48h the window was open). All 6 acceptance criteria satisfied at promotion time. RFC ladder state: 0001–0012 all Accepted (12 total). 0000 is the template scaffold. Future RFCs revert to the canonical 7-day comment window once MAINTAINERS.md lists a non-steward maintainer.
  • RFC 0012 (Memory Compaction Profile) Phase 3 prep landed 2026-05-14 (promoted to Accepted 2026-05-15 under the bootstrap waiver above):

- Reference hostexamples/hosts/postgres/src/memory-adapter.ts gains runCompaction + applyCompactionRedaction + REFERENCE_COMPACTION_CAPABILITY. Server.ts conditionally advertises capabilities.memory.compaction when OPENWOP_MEMORY_COMPACTION=true and exposes the test seam at POST /v1/test/memory/{seed,compact} when OPENWOP_TEST_TRIGGER_COMPACTION=true. SR-1 carry-forward (RFC 0012 §D) honored by re-substituting [BYOK:...] form-leaks + non-canonical <REDACTED:...> markers with [REDACTED:carry-forward-<n>] BEFORE the derived entry persists. Output entries carry the compacted-from:<id> provenance tag per §C. - 3 conformance scenariosmemory-compaction-event-emitted.test.ts (canonical §B payload shape), memory-compaction-sr1-carry-forward.test.ts (load-bearing §D — replaces the Phase 2 it.todo() stub), memory-compaction-provenance-tag.test.ts (soft assertion on §C). All three gate on capabilities.memory.compaction.supported + test seam reachability. 3/3 pass live against the Postgres reference host. - Host smokeexamples/hosts/postgres/test/memory-compaction.test.ts verifies 7 paths end-to-end (advertisement + seed + compact + outputId readability + SR-1 §D + provenance + empty-noop).

  • RFC 0012 (Memory Compaction Profile) Draft → Active (2026-05-13) — opens the 7-day public comment window (closes 2026-05-20). New optional capabilities.memory.compaction advertisement + memory.compacted canonical event + SR-1 carry-forward invariant for any host that distills short-lived MemoryEntry rows into longer-lived ones. Additive per COMPATIBILITY.md §2.1.
  • Tarball-fetch + signature-verify roundtrip vs packs.openwop.dev (2026-05-13)conformance/src/scenarios/registry-public.test.ts gains a 4th describe block that fetches core.openwop.examples@1.0.0's tarball + .sig + publisher public key from the live registry, asserts SRI integrity matches a fresh SHA-256 of the tarball bytes, and runs Ed25519 verification per node-packs.md §"Signing recipe" (method=ed25519 signs the whole tarball). Closes coverage.md row 34's "Remaining: tarball-fetch + signature-verify roundtrip" gap. 6/6 tests pass against live packs.openwop.dev.
  • Strict-mode opt-out signaling (2026-05-13) — new OPENWOP_OPTED_OUT_PROFILES=name1,name2 env var consumed by conformance/src/lib/behavior-gate.ts distinguishes "host opted out (honest minimal posture)" from "host claims but doesn't deliver (bug)". Strict mode (OPENWOP_REQUIRE_BEHAVIOR=true) skips opted-out profiles with a "honest opt-out" log line instead of failing. SQLite + Python reference hosts can now achieve strict-mode green without falsifying capability claims. Advertise + opt-out conflict surfaces a loud warning so typos don't mask real bugs.
  • Batch A — adopter-facing prose refresh (2026-05-13):

- examples/hosts/postgres/conformance-full.md + INTEROP-MATRIX.md re-measured against suite v1.1.0 with conditional-profile env vars: 781/850 (91.9% total, 95.2% of non-todo, 96.4% of applicable) — up from 728/797 the prior measurement. +53 scenarios + +53 passes net of Phase H/I capability surfaces + 9 stage5 vendor packs. One failure remains: documented webhook-signed-delivery flake (passes in isolation; full-suite timing collision). - docs/migration/v1.0-to-v1.1.md — new adopter-facing "what's new" guide. Documents v1.1 as purely additive per COMPATIBILITY.md §2.1: every v1.0 conformance pass remains valid, no code changes required for v1.0 implementations. Per-capability sections walk through Phase H (BYOK / AI providers / MCP / HTTP / cap-breach kinds) + Phase I (memory / agents / auth profiles) + Phase G (spec-corpus close-out) with cross-links to RFCs + conformance scenarios. Linked from README.md §"Document index". - ROADMAP.md §"v1.2 outlook (projected)" — new gate-conditioned projection of v1.2 candidates: RFC 0012 memory compaction, WASM Component Model sub-RFC, Rust SDK v0.1 (demand-gated), 4 audit-gated core.openwop.* packs, cross-host SSE replay, mTLS termination on Postgres, multi-region idempotency end-to-end fixture. Each item carries its specific gate (RFC comment window / external audit / capability flag / adopter ask) — no fixed calendar; items move to next minor or Withdrawn if no signal. - sdk/python/QUICKSTART.md + sdk/go/QUICKSTART.md — new 5-minute end-to-end walkthroughs that boot the in-memory reference host on your laptop, run a workflow against it, and read the event log. Both READMEs link to the new quickstarts.

  • Batch C — conformance coverage close-outs (2026-05-13):

- Multi-region idempotency convergence-rule resolver (Track 13) — new examples/hosts/postgres/src/multi-region.ts ships the canonical algorithm for idempotency.md §"Multi-region idempotency" §"Convergence rule": lex-min(runId) wins, losers get run.cancelled { reason: 'cross_region_dedup_loss' }, every region's cache redirects to the winning runId. Pure function — same inputs → same outcome regardless of caller order, region, or wall clock; two regions running the resolver independently arrive at the same survivor without coordination. Smoke test (test/multi-region-idempotency.test.ts) verifies 6 paths including label-determinism for the operator-tier openwop.idempotency.cross_region_conflicts_total counter. Conformance scenario (multi-region-idempotency.test.ts) extended to also verify that hosts claiming crossRegion: 'best-effort' or 'strict' advertise the operator metric per §"Operator surface". The Postgres reference host stays single-region (crossRegion: 'single-region'); the resolver is operator-adoption-ready for any future multi-region host. - Cross-host trace-context propagation across core.subWorkflow (Track 11 remaining row) — new conformance/src/scenarios/otel-trace-propagation-subworkflow.test.ts closes the previously-partial gap on coverage.md row 52. Asserts: when a parent run is started with an inbound traceparent and contains a core.subWorkflow node, the dispatched child run's spans MUST share the parent's traceId. Distributed traces stitch across the dispatch boundary without operator-side correlation hacks. Gates on capabilities.observability + conformance-subworkflow-parent fixture advertisement + OPENWOP_OTEL_COLLECTOR=true. coverage.md Observability row + per-scenario row both flipped to A (full coverage).

  • Batch B — Postgres reference host additive surfaces (2026-05-13):

- Phase I.2 reasoning-event emission wiring — Postgres host's core.llm.chat / core.llm.completion executors now emit agent.reasoned (verbosity-gated per RunOptions.configurable.reasoningVerbosity → host default fallback "summary" with 512-token cap) + agent.decided (confidence ∈ [0,1]) after a successful AI-proxy call. core.mcp.toolCall emits agent.toolCalled BEFORE the call (carrying argumentsSha256) and agent.toolReturned AFTER (paired via shared callId, with outcome.{resultSha256,resultLength,isError,durationMs} on success or error.{code,message} on failure). SR-1 + MCP-1 preserved end-to-end: only SHA-256 digests + lengths + outcome flags appear on payloads — never raw tool arguments or result content. Verified by examples/hosts/postgres/test/reasoning-event-emission.test.ts via two new host-private fixtures (loaded through the OPENWOP_EXTRA_FIXTURES_DIR test seam — these typeIds are implementation-specific and not yet protocol-normative). - Phase I.7 mTLS termination — Postgres host now claims openwop-auth-mtls end-to-end when OPENWOP_MTLS_CERT_PATH + OPENWOP_MTLS_KEY_PATH are set. HTTP listener switches to node:https.createServer({ requestCert: true, rejectUnauthorized: OPENWOP_MTLS_REQUIRED !== 'false' }); OPENWOP_MTLS_CA_PATH is optional (when present, only client certs signed by that CA bundle pass the handshake). Discovery emits capabilities.auth.mtls.{supported: true, required: <bool>, subjectMapping: 'cn'} only when configured (honesty principle). Verified end-to-end by test/mtls.test.ts (advertisement shape + valid-cert 201 + no-cert TLS handshake rejection). The existing conformance/src/scenarios/auth-mtls.test.ts now flips from "Not claimed" to a verified positive path when the Postgres host is launched with OPENWOP_MTLS_* configured.


1.1.0

2026-05-12

OpenWOP v1.0 close-out + additive features

The close-out release for v1.0. The protocol contract was frozen on 2026-05-08 (see the spec-freeze entry below) and first published as v1.0.0 on 2026-05-11 (see entry below). This 1.1.0 release closes every controllable gap from the 2026-05-10 deep-dive review and the 2026-05-12 architectural re-evaluation, hardens the Postgres reference host to production-runtime parity, and lands 18 additive feature surfaces (Phase H launch-blockers + Phase I enterprise-blockers).

All changes in this release are additive per COMPATIBILITY.md §2.1 — no existing required fields changed type or optionality, no event-type shape changed, no endpoint contract relaxed, no existing MUST weakened. Hosts that were v1.0.0-compliant remain v1.x-compliant; this release just adds new capability surfaces that hosts may now advertise + new conformance scenarios that gate on those advertisements.

Per-track closure status is tracked in docs/PROTOCOL-GAP-CLOSURE-PLAN.md (archived 2026-05-12); per-host conformance evidence lives in examples/hosts/*/conformance.md + INTEROP-MATRIX.md.

Spec corpus state

  • 29 prose specs at Status: FINAL v1. Zero DRAFT / STUB / OUTLINE tags remain. New additions since 2026-05-08 freeze: auth-profiles.md, capabilities-change-detection.md, grpc-transport.md, i18n.md, compliance.md, host-capabilities.md, production-profile.md, replay.md retention/expiry annex, node-packs.md lockfile + Component-Model annexes.
  • 22 first-class JSON Schemas under schemas/, all JSON Schema 2020-12 with $id at https://openwop.dev/spec/v1/<name>.schema.json and additionalProperties: false on every object. New: agent-manifest, agent-ref, memory-entry, memory-list-options, audit-verify-result, pack-lockfile, orchestrator-decision, dispatch-config.
  • OpenAPI 3.1 (api/openapi.yaml) — every endpoint has operationId + tags + ≥ 1 error response; every schema referenced via cross-file $ref. Lints clean under redocly lint. New operations: verifyAuditLog, bulkCancelRuns.
  • AsyncAPI 3.1 (api/asyncapi.yaml) — every channel binds to a message + payload schema reference. Lints clean under asyncapi validate.
  • gRPC transport profile (api/grpc/openwop.proto + spec/v1/grpc-transport.md) — canonical openwop.v1.Engine service; profile-gated via capabilities.supportedTransports: ["grpc"].

RFCs landed

  • RFC 0001 — RFC process itself (Accepted).
  • RFC 0002 — Agent identity + reasoning events (Accepted).
  • RFC 0003 — Agent packs (Accepted).
  • RFC 0004 — Memory layer + MemoryAdapter contract (Accepted).
  • RFC 0005 — Conversation as run primitive (Accepted).
  • RFC 0006 — Orchestrator-supervisor role (Accepted).
  • RFC 0007core.dispatch core node (Accepted).
  • RFC 0008 — WASM ABI (Accepted 2026-05-13) + Component-Model variant annex.
  • RFC 0009 — Production-profile conformance (Accepted 2026-05-12).
  • RFC 0010 — Auth-profile conformance + v1.0 closure umbrella (Accepted 2026-05-12).
  • RFC 0011 — Auth-scoped discovery (Accepted 2026-05-12).

Multi-Agent Shift (RFCs 0002–0007 + RFC 0008)

  • Phase 1 — AgentRef wire shape; agent.reasoned / agent.toolCalled / agent.toolReturned / agent.handoff / agent.decided events; confidence escalation contract (CP-1); message reducer.
  • Phase 2 — Agent capability discovery on /.well-known/openwop; pack.json agents[] extension; agent-pack manifests.
  • Phase 3 — Agent memory layer: memoryRef resolution + redaction (SR-1) + cross-tenant isolation (CTI-1) + host MemoryAdapter contract.
  • Phase 4 — Conversation as run primitive: conversation.start / conversation.exchange / conversation.close suspend variants.
  • Phase 5 — Orchestrator-supervisor: core.orchestrator.supervisor typeId + OrchestratorDecision schema + runOrchestrator.decided event.
  • Phase 6 — core.dispatch core node: conservative dynamic graph mutation (CP-2); causationId propagation per RFC 0007 §E.
  • WASM ABI — RFC 0008 Active; reference Rust pack at examples/packs/rust-hello/ (28 KiB wasm32); Wasmtime-free loader at examples/hosts/in-memory/src/wasm-loader.ts; six conformance scenarios; deliberately-misbehaving packs for memory-cap (examples/packs/rust-misbehaving-memory/) and ABI-mismatch (examples/packs/rust-misbehaving-abi/) positive-path testing. Schema extension: capBreached.kind enum gained wasm-memory, wasm-fuel, wasm-execution-time (RFC 0008 §K). New optional capability capabilities.nodePackRuntimes.wasm.loadedPacks[] surfaces accepted pack names; rejected packs (declared ABI not in abiVersions[]) MUST be absent — drives the conformance positive path since rejection happens at load time before any node-invoke surface.
  • OTLP/gRPC collector (Track 11 closure) — conformance/src/lib/grpc-framing.ts (hand-rolled length-prefixed gRPC HTTP/2 framing, zero npm deps) + OtelCollector.startGrpc() (parallel node:http2 server, shared spans/metrics store). New optional capability capabilities.observability.otel.exportProtocols[] advertises the supported OTLP transports (http/json, http/protobuf, grpc); spec/v1/observability.md gains a §"Export protocols" normative section. New conformance scenario otel-emission-grpc.test.ts gates on the array. Opt-in via OPENWOP_OTEL_COLLECTOR_GRPC=true (default port 4317).

Capability surfaces

Hosts advertise optional behaviors at /.well-known/openwop. New capability blocks added between 2026-05-08 and 2026-05-12:

  • capabilities.runs.{pauseResume, bulkCancel} — pause/resume + bulk-cancel endpoints.
  • capabilities.webhooks.{supported, signatureAlgorithms} — HMAC v1 signing ({timestamp}.{rawBody}).
  • capabilities.secrets.{supported, scopes, resolution} — BYOK secret resolution (host-managed).
  • capabilities.aiProviders.{supported, byok, policies} — AI provider routing with 4-mode policy enforcement (disabled / optional / required / restricted).
  • capabilities.mcpClient.{supported, transports, trustBoundary} — MCP tool invocation; trustBoundary: "untrusted" per threat-model-prompt-injection.md §UNTRUSTED.
  • capabilities.httpClient.{supported, methods, ssrfGuard, maxResponseBodyBytes} — universal core.http.request typeId with SSRF guard.
  • capabilities.memory.{supported, maxEntrySizeBytes, ttlSupported}MemoryAdapter read-side contract per RFC 0004.
  • capabilities.agents.{supported, profile, modelClasses, orchestratorPattern, memoryBackends, orchestrator, dispatch, reasoning} — Multi-Agent Shift Phase 1–6 advertisement.
  • capabilities.auth.{profiles[], rotation, oauth2, oidc, auditLogIntegrity} — auth-profile advertisement (rotation; OAuth2-CC; OIDC user-bearer; audit-log integrity).
  • capabilities.discovery.authScoped.{supported, mode} — RFC 0011 same-endpoint auth-scoped discovery.
  • capabilities.production.{supported, backpressure, retention, debugBundle} — production-profile claim (RFC 0009).
  • capabilities.observability.{otel, metrics} — OTel emission with openwop.{run.backlog, queue.depth, run.duration} metrics; OTLP/HTTP-JSON + OTLP/HTTP-protobuf encodings supported.

Reference SDKs at 1.1.0

  • @openwop/openwop (TypeScript, npm) — first-class methods on OpenwopClient for every OpenAPI endpoint; HTTP_ERROR_CODES catalog with 40+ canonical codes; RunEventDoc type + isTerminalRunStatus helper; new typed exports added in 1.1.0: MemoryEntry, MemoryListOptions, AgentRef, AgentsCapability, AuthProfileClaim, AICredentialRef, McpToolCallNodeConfig, HttpRequestNodeConfig.
  • openwop-client (Python, PyPI) — stdlib-only port preserving the same surface; HTTP_ERROR_CODES frozenset; matching wire types.
  • github.com/openwop/openwop/sdk/go (Go modules) — same surface; HTTPErrorCodes slice; doc comments on every exported symbol; go vet clean.
  • Rust SDK — foundation demand-gated; conformance suite is language-agnostic black-box, so future Rust client tests against the same wire contract.

Reference hosts

Four reference implementations live under examples/hosts/. Conformance evidence per host in INTEROP-MATRIX.md:

  • In-memory (TypeScript, examples/hosts/in-memory/) — local-dev fastest-boot; no persistence; claims openwop-core + stream profiles.
  • SQLite (TypeScript, examples/hosts/sqlite/) — single-machine durability; 669/731 (91.5%) conformance pass rate; claims audit-log-integrity + 4 interrupt profiles + auth-api-key-rotation + discovery-auth-scoped.
  • Python in-memory (Python 3.11 stdlib-only, examples/hosts/python/) — cross-language portability proof; 700/788 (100% of applicable, ZERO failures) conformance pass rate.
  • Postgres (TypeScript, examples/hosts/postgres/) — production durability path; first host claiming openwop-production; 730/799 (91.4%) conformance pass rate. Ships with BYOK + 4-mode AI policy + MCP client + HTTP client (SSRF-guarded) + MemoryAdapter + agents capability + API-key rotation + auth-scoped discovery + OAuth2-CC + OIDC user-bearer JWT validators (RS256 + ES256 with JWKS cache + alg: "none" rejection) + cap-breach enforcement + per-workflow configurableSchema validation + subworkflow outputMapping + parent linkage.

Conformance suite at 1.1.0

  • @openwop/openwop-conformance — 103 scenario files under conformance/src/scenarios/. New since the 1.0.0 publish: production-profile (backpressure + retention-expiry), auth profiles (api-key-rotation + OAuth2-CC + OIDC + mTLS shape), audit-log integrity, BYOK roundtrip, MCP/A2A real-impl interop (verified against @modelcontextprotocol/server-everything + A2A 0.3 JSON-RPC reference), agent memory (roundtrip + cross-tenant + redaction + TTL), webhook signed delivery, stream-modes (buffer + mixed-mode), bulk-cancel, MCP-toolcall redaction, HTTP-client SSRF, WASM pack ABI-version-rejection + memory-cap positive-path, configurableSchema positive overlay, pause-resume race + drain semantics.
  • Two execution modes: npm test (parallel files, ~95s) and npm run test:strict (--no-file-parallelism for production-backpressure + OTel envelope coverage).
  • Behavior-gated: OPENWOP_REQUIRE_BEHAVIOR=true flips capability-gated scenarios from skip to fail when the host doesn't advertise the profile.

SECURITY invariants

  • 68 invariants tracked (SECURITY/invariants.yaml):

- 35 protocol-tier (all with public conformance tests; CI-gated via scripts/check-security-invariants.sh). - 32 reference-impl tier (verified by each reference impl's own CI). - 1 advisory (defense-in-depth, no hard MUST).

  • New protocol-tier invariants added between freeze and release: mcp-toolcall-payload-redaction, http-client-ssrf-guard, agent-memory-cti-1, agent-memory-sr-1-redaction, auth-key-rotation-no-canary-echo.
  • Threat-model docs at SECURITY/threat-model-*.md (secret-leakage, prompt-injection, provider-policy, node-packs, auth-profiles).
  • CNA registration + bug-bounty program annex at SECURITY/cna.md + SECURITY/bug-bounty.md.

Wire-shape stability

The wire contract remains frozen at v1 per COMPATIBILITY.md §2 — additive changes only inside v1.x, safety-fix only when correctness or CVE-class issues require it. Breaking changes wait for v2. This 1.1.0 release adds new optional capability surfaces; hosts that advertised the 1.0.0 capability set remain v1.x-compliant without change.

Domain and package naming

  • Canonical domain: openwop.dev
  • Registry: packs.openwop.dev (TLS cert provisioned; live)
  • Package names: @openwop/openwop, @openwop/openwop-conformance, openwop-client, github.com/openwop/openwop/sdk/go — stable through any v1.x release per PUBLISHING.md.

Verification

npm run openwop:check — the 8-step pre-merge gate — passes for every commit on main:

1. TypeScript reference SDK builds + emits dist/ 2. Conformance suite typechecks + server-free scenarios pass 3. Python reference SDK syntax + import smoke clean 4. Go reference SDK go vet + tests clean 5. OpenAPI 3.1 redocly lint clean 6. AsyncAPI 3.1 asyncapi validate clean 7. Publish-metadata + npm-pack-contents + Python/Go release-surface clean 8. SECURITY invariants — every protocol-tier MUST-NOT has a public test


1.0.0

2026-05-11

OpenWOP v1.0 first publish

First publication of the openwop spec corpus to the package registries. Captures everything that was in scope at the v1 spec freeze (2026-05-08) plus three days of pre-publish hardening: SQLite host conformance fixes, registry TLS provisioning, audit-log integrity profile shipped end-to-end on SQLite, CI gate hardening (NPM_CACHE / GOCACHE cross-platform), recruitment artifacts for first non-steward host + pack-author.

Published artifacts

  • npm: @openwop/openwop@1.0.0 (TypeScript SDK), @openwop/openwop-conformance@1.0.0 (conformance suite). Published 2026-05-11 05:06–05:09 UTC.
  • PyPI: openwop-client@1.0.0 (Python SDK).
  • Go modules: tagged sdk/go/v1.0.0 on origin.
  • Tag: v1.0.0 on origin at commit 6a637f1.

Scope at 1.0.0

  • Spec freeze content per [1.0] — 2026-05-08 entry below — 26 prose specs at FINAL v1; 17 first-class JSON Schemas; OpenAPI 3.1 + AsyncAPI 3.1; three reference SDKs (TS/Python/Go); conformance suite v1.0.0.
  • Phase A conformance behavior closure — SQLite host pass rate 91.5% under OPENWOP_REQUIRE_BEHAVIOR=true.
  • Phase B spec corpus completion — all DRAFT/STUB/OUTLINE tags retired; host-capabilities.md promoted; i18n.md + compliance.md annexes shipped.
  • Phase C round 1 — three reference hosts (in-memory, sqlite, python) advertising their respective capability surfaces.
  • Phase F — MCP + A2A probe extensions (synthetic fakes).
  • Registry — packs.openwop.dev live with TLS; 3+ packs published with Ed25519 chains.
  • CI — npm run openwop:check 8-step gate green.

Known gaps at 1.0.0 (closed in 1.1.0)

  • Postgres reference host had not yet shipped the BYOK / MCP / HTTP / agent-memory / OAuth2-CC / OIDC / API-key-rotation / auth-scoped-discovery surfaces.
  • 11 conformance scenarios were shape-graded (not behavior-graded).
  • Phase F real-impl interop (against @modelcontextprotocol/server-everything + A2A 0.3 reference) was not yet wired.
  • Phase H launch-blockers + Phase I enterprise-blockers from the 2026-05-12 architectural re-evaluation were not yet identified.

1.0

2026-05-08

OpenWOP v1 spec freeze

Protocol contract locked. The spec corpus, schemas, API definitions, reference SDKs, and conformance suite all reach 1.0 artifact versions. This date marks the freeze — no breaking wire-shape changes after this point inside v1.x.

The 4-day window between this freeze and the 2026-05-12 release closes every controllable gap from the deep-dive review and hardens reference hosts to production-runtime parity. See the [1.0.0] release entry above for the consolidated record.

What's locked at freeze

  • Prose specs — 26 docs at Status: FINAL v1: auth.md, capabilities.md, channels-and-reducers.md, idempotency.md, interrupt.md, node-packs.md, observability.md, replay.md, rest-endpoints.md, run-options.md, stream-modes.md, version-negotiation.md, profiles.md, scale-profiles.md, debug-bundle.md, host-extensions.md, a2a-integration.md, mcp-integration.md, and the v1 profile/addendum docs.
  • JSON Schemas — 17 first-class schemas including agent-ref, agent-manifest, memory-entry, memory-list-options, conversation-turn, conversation-event, and dispatch-config schemas.
  • API definitions — OpenAPI 3.1 (api/openapi.yaml) + AsyncAPI 3.1 (api/asyncapi.yaml).
  • Reference SDKs at 1.0@openwop/openwop (TypeScript), openwop-client (Python), openwopclient (Go).
  • Conformance suite at 1.0@openwop/openwop-conformance.
  • CI gatingscripts/openwop-check.sh + .github/workflows/openwop-spec.yml.
  • GovernanceCONTRIBUTING.md, GOVERNANCE.md, MAINTAINERS.md, COMPATIBILITY.md, SECURITY.md.

Multi-Agent Shift (Phases 1-6 landed by freeze)

  • Phase 1 (RFC 0002) — Agent identity (AgentRef), agent reasoning + tool + handoff event family, confidence-escalation contract, message reducer.
  • Phase 2 (RFC 0003) — Agent capability discovery on /.well-known/openwop + pack.json agents[] extension.
  • Phase 3 (RFC 0004) — Agent memory layer — memoryRef resolution, redaction guarantees, host MemoryAdapter contract.
  • Phase 4 (RFC 0005) — Conversation as run primitive — conversation.start / conversation.exchange / conversation.close.
  • Phase 5 (RFC 0006) — Orchestrator-supervisor role — core.orchestrator.supervisor node type.
  • Phase 6 (RFC 0007)core.dispatch core node — conservative dynamic graph mutation.

Domain and package naming

  • Canonical domain: openwop.dev
  • Registry: packs.openwop.dev
  • Package names: @openwop/openwop, @openwop/openwop-conformance, openwop-client, openwopclient