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 / FINALvocabulary 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 to1.1.7. A deep-link like#v1-1-6therefore has no anchor here by design — the TS SDK's own history lives insdk/typescript/CHANGELOG.mdand on npm. Current published per-package versions (as of the1.1.7corpus release):@openwop/openwop1.2.0 (npm) ·@openwop/openwop-conformance1.18.1 (npm) ·openwop-client1.1.7 (PyPI) ·github.com/openwop/openwop/sdk/gov1.1.7 (Go modules).
Unreleased · unreleased
UnreleasedCONTRIBUTING.mdgains "An RFC MUST NOT state a rule a core doc owns" — open the owningspec/v2/core/*.mdand grepconformance/src/scenarios/before landing normative RFC prose, becauseopenwop-check.shcross-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 citedevents.mdand 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
typecolumn by anemitarity bug, fixed at source and not backfilled. The real exposure is 1273 rows underai.,node.andconversation.*— and the codemap's v1 side showsnode.(10 rows) andconversation.(3 rows) are namespaces the protocol owns, sonode.progressis a host-invented event inside protocol space. Passing it through would makenode.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 inspec-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" — andv2-unmapped-type-refusedwitnesses it on both halves. RFC 0176's migration rowopenwop.migration.C9.3names 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, andopenwop-check.shpassed 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 underevents.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 uniquenodeId)". The seam is keyed bynodeIdalone — no run, no workflow, no tenant — so that parenthetical was the entire safety argument, and the corpus contradicted it: nine fixtures declared a node calledstructured-call. Vitest runs scenario files in parallel, so any two of the nine overwrote each other's program mid-run. This is the long-livedreplay-observable-sequence-determinismflake — a scenario that never programs the mock, runsconformance-phase4-nondet-tool, and inherits whatever the last writer left; with a refusal staged by an envelope scenario its source run reachedfailedand it reportedexpected '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, andconformance/scripts/check-mock-ai-node-ids-unique.mjsenforces 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 tospec/v2/path-manifest.jsonnow requires updating a pin, because it silently movesversioning.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:packsis 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/packsit 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 fails500 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 defectpersistence.mdalready 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.mjsrefuses to promote a scenario tomajors: [1, 2]while it gates on a capability.supportedseat.capabilities.mdline 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 recordsinapplicableforever 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 → Acceptedon tier-1 evidence: the reference host (openwop-examplescc2d181, suite 2.1.1, CI run 34558191209) advertisesrunListand passesv2-run-list3/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-13a 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-12two 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§"TheengineVersionaxis 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-12a 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-11what `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.mjsgives the generatedsinceproperty
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-11a 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-honoredleg 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-11the 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.jsonregenerated: 517 files, 74 targeting
major 2 (v2-run-list included). First real run against the reference host: 3/3 legs pass.
scripts/openwop-check.shnow runsgenerate-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-11a 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 /runsreturns{ runs: RunSnapshot[], nextCursor? }— the caller's runs, newest first, tenant-scoped by construction with everyrunIdbound (identity.md §5);limithonoured up to the advertisedrunList.maxPageSize; opaquecursor, a cursor the host did not mint is400 validation_error;workflowIdandstatusexact-match filters whenrunList.filtersnames them. Gated on the new core familyrunList(facetsmaxPageSize,filters;404 not_foundwhen unadvertised). Newschemas/v2/run-list-response.schema.json;runs.md§Surface row + §List;capabilities.md§ runList;derive-v2-api.pyemits the operation; path manifest gains its 52nd operation. - Conformance
v2-run-list(gated onrunList, three legs): the two runs the caller just created appear and every id carries the caller's tenant segment; no page exceedsmaxPageSize; a foreign cursor is refused; aworkflowIdfilter 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 advertisesmyndhyve.vendor-namespace = { root, twin, roots[], rfc }; verified on the wire by the steward. §Unresolved-1 corrected:GET /runslist andDELETE /runs/{runId}were never v1 protocol operations (onlycreateRun/getRunexist 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,rfccommon 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-10host-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 inspec/v2/declaration.jsonextensions; no major in the path; never a protocol operation, never measured; advertised underextensions.<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— orgsopenwop-appandmyndhyveregistered (both hosts already emit vendor codes and event types under those names; onlyexamplewas registered);reservedOrgsgains 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 (noOpenWOP-Version, notapplication/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-10the 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.devatf7c631b9b(servingopenwop-app-backend-00688-9bk), suite2.0.11, signedopenwop-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). Bothv2-poll-cursor-v2fails closed (ADR 0633); the 25 seams-gatedblockedrows areinapplicableon 2.0.10+ (profile not advertised, honestly); the newopenwop.requirement.0170.id-grammar.bare-idrow 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-standardwitnessCount 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):evidenceSecretsFromEnvclassifiedOPENWOP_BUNDLE_SIGNING_KEY_IDas a secret because its name matchesKEY, and redacted the publishedkeyIdout of the embeddeddiscovery.documentafterdiscovery.sha256was 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_IDare now excluded, the emitter names the keyId it publishes asexcept, 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-10three 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.json—prompts.renderEndpointsaid "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:renderin 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 affordancev2-dual-stack-negotiationhas required since rc.44. It expires with the overlap: a host advertising no1.xmember MUST refuse the bare form400 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 (no1.xadvertised) it requires400 validation_error. The branch is decided from live discovery, so the leg witnesses both the affordance and its expiry.
2.0.9
2026-09-10the 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.0was 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.mdgains 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/principalKind → owner.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-07the 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-06three 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.7 —
vendorControlGaterecordsblockedrather
than inapplicable for an unresolvable registry. See conformance/CHANGELOG.md.
2.0.6
2026-09-06the 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-installablenow 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-06four 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.mdcited 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-refuseddrove 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: truewas 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-artifactsshipped 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-stableismajors: [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-06a 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-conflictnow drives it. - The v1 scenario keeps the
githubfixture, and that was checked rather than assumed.connection-provider-resolutionismajors [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-ingithubstill 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-06a 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-refireselected on a BRANCH permission to witness a REPLAY obligation. The target filter wasr.guarded === true && r.branchReFires === false.replay.md:78forbids 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 withbranchReFires, whose own schema description reads "RFC 0140 §G6 — abranchfork re-fires this seam by design". Everyguarded: truerow is a valid target; the filter now selects onguardedalone. Underneath it sat a second bug:branchReFiresis optional (required: ["seam","kind","guarded","guardedBy"]), so=== falsealso excluded every row merely silent on the permission. Found by the reference host, which holds ten honestbranchReFires: truerows — every seam it owns genuinely re-fires on a branch — and was about to buildfireEffectSeamfor a scenario that would have gone on recordinginapplicableafter the work landed. It asked rather than inventing afalserow, 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 attemptFAIL_FIRST + 1, so reaching it costs the sum of the firstFAIL_FIRSTbackoff intervals, not the largest one: on an exponential-from-30s policy,30 + 60 = 90 sagainst aRETRY_WAIT_CAP_MSof exactly90_000. A host loses by the width of one delivery. The obvious fix cannot be built:spec/v2/facets/webhooks.schema.jsonretryPolicyisadditionalProperties: falseover 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 recordsblockedwith the arithmetic in its reason: the obligation was taken on and the suite could not measure the outcome (RFC 0148 §A), rather thaninapplicable, 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:
| behaviour | still caught? |
|---|---|
| never retries at all | yes — attempts.length > 1, both legs, unconditional |
| retries unboundedly past its advertised budget | yes — the dead-letter leg's attempts.length <= maxAttempts, whenever maxAttempts is advertised |
| retries within budget and never lands a success | no — this is the detection actually lost |
retries unboundedly while advertising no maxAttempts | no, 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.mdships inside the published tarball, so editing 2.0.3's entry there after@openwop/openwop-conformance@2.0.3was published would leave the tree claiming a version whose bytes differ from npm's —check-published-suite-identityrefused 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-062.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-pass → executed-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 bothit()blocks on the harness default —conformance/vitest.config.tstestTimeout: 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 runswaitTerminalplus two sequentialretryWaitMswaits, so it needed up to 191 s of a 30 s budget. - It took a passing row with it.
dead-letterpassed on 2.0.0-rc.67 by observingattempts.length > 1inside the old 20 s window. 2.0.1 moved that leg ontoretryWaitMstoo, 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_MSandDEAD_LETTER_TEST_TIMEOUT_MS = RETRY_WAIT_CAP_MS * 2 + WAIT_SLACK_MS, passed as eachit()'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-05the 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()readtriggerBridge.retryPolicy, and its docstring asserted that was "the only v2 carrier".spec/v2/facets/webhooks.schema.jsonstates the opposite in as many words: "retryPolicy is the v2 carrier of the delivery obligation (wastriggerBridge.retryPolicyat v1)", with the field's own description adding "The webhooks family carries it at v2;triggerBridge.retryPolicyis the v1 carrier and stays through the overlap." So a host correctly advertising the v2 carrier had its policy read asnull, and one still on the v1 field was measured against a different subsystem's budget. The reporting host advertisesmaxAttempts: 8for 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 readswebhooks.retryPolicyfirst, falling back totriggerBridge.retryPolicyfor 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-failon 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 forfixed/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-05openwop 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-v2was flaky, and its flake blamed the host — on a core-standard floor file, so certification was non-deterministic (suite2.0.0-rc.67; found bymyndhyve, 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 — butterminalRun()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 reportsexpected [ 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 recordsblockednaming 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.jsonfileslistsCHANGELOG.md, which resolves inside the package, so the published file isconformance/CHANGELOG.md— a different file from this one, which is not packed. The shipped file stopped at1.156.0(2026-09-02) and carried zero mentions of any2.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 intoopenwop-check.sh) now fails whenever a packed CHANGELOG's newest heading is not its own package'sversion; 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.mdships inside the published conformance package". That is wrong — the rootCHANGELOG.mdis 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-appthe 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 inv2-effect-seam-no-refireand was not predicated on any host's shape. (Wording supplied by the host; the rc.64 entry is not edited becauseCHANGELOG.mdships 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-prefixis ruled OUT of theopenwop-core-standardfloor, with the general test written down (suite2.0.0-rc.65; the open question from rc.60).check-declarationrule 10 admits the file mechanically — it gates only onreplay, 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 singleitgated entirely on theconformance-multi-nodefixture, and no spec text makes that fixture mandatory, so an honestreplayholder 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-itfile, so the file still witnesses — the rc.59partial-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-refirepassed on an empty ledger —0 <= 0certified suppression it had never observed (suite2.0.0-rc.64; found byopenwop-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 inreplaymode, and assertedcountOf(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 recordsblockedwhen 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 theblocked-versus-inapplicablerule 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.jsonnow says whatstateobliges (suite2.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 onlyclaimedandcompleted, and that is schema-valid and witness-sufficient. Verified rather than asserted: no v2 scenario or corpus script readsstate;v2-effect-seam-no-refirecompares row counts andv2-effect-identity-business-keyreadseffectId/providerKeyacross attempts. Soreleasedandescapedare 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 bymyndhyvereading its own bundle, the guard defect found by sabotage-proving the correction). rc.62 changed the fifteen scenarios that callseamsProfileAdvertiseddirectly. It missedconformance/src/lib/era2-seed.ts, whoseera2Gatemints the sameblockedone 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.era2Gatenow recordsinapplicable; theblockedit 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-declarationrule 11 was scoped to scenario bodies, so it passed while the rot persisted. A disposition minted in a sharedlib/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 underconformance/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
returnpushed 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 byopenwop-appwhile 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.tsdocuments the vocabulary with these exact examples —softSkip('inapplicable', 'host does not advertise X')versussoftSkip('blocked', 'seam ... not mounted')— and "seams profile not advertised" is the first verbatim. Andv2-manifest-ceiling-refusedgot 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[]" — whileseamsProfileAdvertisedtestsconformance.seamsProfileand the reference host serves noprofiles[]at all. Corrected with a dated erratum; the sentence's actual requirement (the advert names the profile rather than setting atestSeamsboolean) 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 areselftier. Seam-gated obligations stay honestly unwitnessed for a non-seams host: a lowerwitnessCountand aninapplicablerow naming the reason.
Added
check-declarationrule 11 — an absent seams advert MUST beinapplicable(suite2.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 recordsblocked; the advertised-but-404 branch is deliberately not matched. Sabotage-proved: flipping one file back toblockedreddens 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.jsonat buildd029f8fe(openwop/openwop-examples#35): 223 rows, 1551 assertions,openwop-discovery-core(3),openwop-core-standard(13) andopenwop-conformance-seams-v2(4) all certified,check-cut-gates.mjs --host-bundle --host-discovery --networkPASS 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'skind: othernote assertion (vacuous here, since this host declares nootherseam 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, buildingGET /host/effect-seamsagainst 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.jsonrequiresseam/kind/guarded/guardedByand isadditionalProperties: false. The normative prose therefore named three fields the schema forbids and omitted three it requires — a host that implementedreplay.mdliterally 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[].kindhad nodescriptionat all, beside aseamdocumented as "the outbound effect path, host-named" — so a careful reader tookkindfor the wire protocol, which is what its enum looks like. It now says so normatively:kindnames 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-seamswhilecore/replay.md, the schema description,facets/replay.schema.json'sconstandpath-manifest.jsonall 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: smtpandkind: other, and the rule that completeness outranks driveability (suite2.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 whilereplay.mdsays an omitted seam is invisible to the suite, so both options were dishonest.smtpis added as a real distinct egress;otheris 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 carrynotenaming that mechanism — enforced by a schemaif/thenand a new suite assertion, on the same principle as every other honesty rule here: the escape exists and it must state its reason.kindis 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. Insteadreplay.mdnow 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 recordedinapplicablenaming 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 theblocked-vs-inapplicabledistinction 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, theif/thenbinds only rows that opt intoother, 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 (suite2.0.0-rc.60).replay.md§Endpoint says "events withsequence < fromSeqare fixed history; events>= fromSeqare re-executed", and until now the only major-2 scenario that asserted it wasv2-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 aremajors: [1]. A tier-1 host shippedsequence <= fromSeqand 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 settledconformance-multi-noderun at anode.completedstrictly inside its log and asserts the inherited rows are exactly[0, fromSeq)and that the source's event atfromSeqdoes not reappear at that sequence, because it is re-executed. Sabotage-proved: an inclusive prefix (> fromSeq) reddens this leg and only this leg, whilev2-run-fork-refusalsstays 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 floor —
evidence/v2-host-bundles/openwop-host-v2-reference.jsonat build31ec6a12, suite2.0.0-rc.59: 179 / 0 / 0 / 42 over 221 rows and 1545 assertions,openwop-core-standardwitnessCount 8 → 13 and still certified alongside discovery-core (3) and seams-v2 (4),check-cut-gates.mjs --host-bundle --host-discovery --networkPASS 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-standardfloor (suite2.0.0-rc.59;spec/v2/declaration.json→profiles.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,bulkCancelRunsandpauseRunare core operations by the runs.md surface table, so a host that omits one isblockedand the floor refuses it, which is the prose's outcome; the optional legs (valuesmode, the delay and cancellable fixtures) recordinapplicablewith their reason and the file row stays a witnessed pass (partial-witness:marker indetail). 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.jsonis now the rc.58 cut ofopenwop-host-v2-reference@2.0.0-rc.1at763e4e3f(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) allcertified,check-cut-gates.mjs --host-bundle --host-discovery --networkPASS 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--certifyat 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 whosediscovery.urldoes 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.1at3f8bff3d, suite2.0.0-rc.57, signed under the rotated keyv2-reference-2and 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 predateswitnessCountand 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 needswitnessCount ≥ 1, notcertified(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) atf8e24beb7(servingopenwop-app-backend-00678-2sr), suite2.0.0-rc.56, signedopenwop-app-self-2026-09-04and 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-corewitnessCount 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 twov2-poll-cursor-v2rows (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: MyndHyveworkflow-runtimeat847d2425e(servingworkflow-runtime-00330-cjg), suite2.0.0-rc.56, signedmyndhyve-bundle-2026-09and self-verified, 128 / 0 / 45 / 50 / 0 over 223 rows and 1407 assertions,openwop-discovery-corewitnessCount 3 → non-vacuous perspec/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 discoverysigningKeys[]; tarball digest matches); Coexistence FAIL on the seams-gated rows, socertified: 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 ofevidence/v1-end-of-support.json(runbook §5.3).
Fixed
v2-era-2-append-vocabularycould never witness the writer rule — it readeventsoff the response object instead of its JSON body (suite2.0.0-rc.58).eventsOf(before)/eventsOf(after)were handed theOpenWOPResponse(every other caller passes.json), soseedCountwas always 0 and the leg recordedblocked — 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-bundlerecordedblockedin the published layout — a corpus-subject row that denied certification to every npm-installed host (suite2.0.0-rc.57). The scenario readsevidence/corpus-ledger.jsonandsrc/coherence/, neither of which the tarball ships, and asserts nothing about a host; RFC 0148 §A definesblockedover advertised behaviour a missing dependency prevented exercising, and there is none here — the same reasoninglib/spec-coherence.tsapplies to the v1 corpus scenarios. Its early returns now recordinapplicablewith 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.mdnow shows the install as both packages at one explicit version —npm i --legacy-peer-depson the suite alone does not pull the exact-pinned peer (measured ascorpus 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
--checkon its own PR.scripts/generate-v1-eos-clock.mjsrangit 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 syntheticrefs/pull/N/mergecommit) to that commit's timestamp — a date nobody committed — so the coherence check was unsatisfiable, and onmaina 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, elsemain; the script refuses without one), so a PR branch reads "not anchored" until merged — the pre-merge state the row PR commits;--checktolerates 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 withfetch-depth: 0.OPENWOP_EOS_MAIN_REFoverrides the ref for a merge simulation in a scratch worktree. - A leg that said why it returned early was still recorded
blockedatitgranularity — and a bundle with anyblockedrow does not certify (suite2.0.0-rc.56).softSkip()notes were per-file, and the per-itrow insetup.tsconsulted only the journal'sbehaviorGateentries, never the note the leg had just written; soreturn softSkip('inapplicable', 'a2a facet not advertised')produced a per-itrow ofblocked / unclassified returnwhile the file row (which does read the notes) was honestlyinapplicable.verifyBundleV3refusescertifiedProfilesfor any bundle withblocked > 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.tsmarks the sequence at test start (softSkipMark) and reads the notes written during the test (softSkipDispositionSince), and the per-itrule is the pureresolveItRecord— same fold as the file rule (worst-first, reasons joined), a note-less zero-assertion pass still the unclassified return. Pinned bysrc/lib/it-record-softskip.test.ts. No file-level or floor row changes; only per-itrows that were wrong become right. --certify --target-major 2could not write a bundle for any host whose floor files pass (suite2.0.0-rc.55). The flag setOPENWOP_TARGET_MAJORon the vitest child only; the runner's own process never saw it. The worker recorded every v2 floor file underopenwop.floor.<stem>with its assertion count, the runner mapped the same file toopenwop.scenario.<stem>(requirementIdForFilegates ontargetMajor()= process.env), found no ledger row, emitted a report-derivedexecuted-passwith no count, andverifyBundleV3rejected its own bundle as avacuous-passemitter 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 markedpassed. NowresolveTargetMajorsets the runner's own env, andfloorScenarioFiles()also honours the installed v2 floor map (v2FloorsActive()), so the two halves cannot disagree again;src/lib/floor-id-agreement.test.tsruns 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: exportOPENWOP_TARGET_MAJOR=2for the runner process.pause-resume.test.tsleg 1 could not pass on any conforming host (suite2.0.0-rc.54). rc.53 created a 30 s node, paused itimmediate, resumed, and polled for a terminal inside the suite's 30 s test timeout. The resumednode.startedis 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) contradictedspec/v1/rest-endpoints.mdin three places and the fixture contract in a fourth (suite2.0.0-rc.53). The first v1 host to un-skip the file found it: (1) it expected a second:pauseon a paused run to be 200/202, citing an "additive contract" the prose does not contain; the prose says409with the existing pause'spausedAtindetailsunless the request carries the originalIdempotency-Key, in which case202with the cached response — both forms are now witnessed. (2) It assertederror === 'conflict'on the 409s; v1's registry is open and names no code for already-paused or not-paused (onlyrun_terminalfor a terminal run), so the scenario asserts409anddetails.runStatusand no code. (3) It paused a 30 s node underdrain-current-nodeand polled 10 s forpaused; drain lets the executing node reach a terminal first, so the timing-sensitive legs now useimmediate, and a new leg with a 3 s node witnesses the drain semantic itself:node.completedprecedesrun.paused. A host that yields mid-node under drain is the non-conforming one; "cooperative drain" is rejected. (4) It sentinputs.delaySeconds; the fixtures definedelayMs. 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;
drainPolicyhas one vocabulary (suite2.0.0-rc.52; comment window waived as sole steward, logged here). A tier-1 host implementingruns.md§Pause and resume recordednode.failedfor the attempt animmediatepause cut, and a replay fork of the paused-and-resumed run then folded that failure and dead-lettered. The prose now says whatimmediatealready implied: the snapshot is taken between events, the interrupted attempt has no terminal node event, a host MUST NOT recordnode.failed(or any terminal node event) for it, and the resumed run'snode.startedis a fresh attempt. The record of the interruption isrun.paused, whose payload MAY carryinterruptedNodeIdandinterruptedAttempt(additive, kind-bound). And therun.pausedpayload'sdrainPolicyenum, seeded at v2 asdrain | interruptwith a provenance note pointing at v1 text that never said it (v1's event carries nodrainPolicy), becomes the request vocabularyimmediate | drain-current-node; the payload echoes the request's word.v2-run-pause-resume's positive leg asserts both: no terminal node event betweenrun.pausedandrun.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: astreamModeoutside the pattern, a forbidden combination (updates,values) and an unimplemented mode are each400 unsupported_stream_modewithdetails.supportedlisting the individual modes; the bogus mode withAccept: application/jsonis still 400, never 406, because validation runs before content negotiation;updatesstreams 200 (the control);bufferMsyields at least oneevent: batchframe with an arraydata: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; invaluesmode the first frame isstate.snapshot. Ids are 0-based and tested for presence, never truthiness.lib/sse.tssubscribe()gainsextraHeaders(it sent noOpenWOP-Version, so a major-2 caller was served the preferred major). Two prose/registry seams closed in the same PR:unsupported_stream_modegains itsdetails.supportedschema (both v1 and v2 prose required it; the row saidnull);events.md§SSE frames now saysstate.snapshotandbatchare frame names, not event types (neither is in the closedtypeenum, correctly). 515 scenario files (v1 445, v2 71). Suite2.0.0-rc.51.
Fixed
- Two rc.48 readers looked for
detailsundererror; 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 missingdetails.maxRunIdswhen the host answereddetails: { maxRunIds: 100 }correctly — retracted on the bus;v2-run-pause-resume'sdetails.runStatusreader had the same shape. A bulk entry'serroris 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. Suite2.0.0-rc.50; a bulk-cancel red on rc.48 or rc.49 is the reader's.
Changed
- The five
runs.mdambiguities rc.48 filed are resolved in the prose and the registry (suite2.0.0-rc.49; comment window waived as sole steward, logged here). (1) A cancel on a terminal run MUST be409 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 runrun_terminal. (3) Inside a bulk-cancel entry identity.md §5 applies exactly as on a path: a foreign tenant segment isid_tenant_mismatch(ornot_found),run_forbiddenis for a same-tenant run the caller may not cancel,run_terminalfor a terminal one. (4) Annotations on a host withoutfeedbackanswer404 not_found, asgetEvalSummaryandgetRunAncestrydo; the table's501had no registered code. (5) TheIf-None-Matchheader note is widened from the discovery document to every resource that carries anETag, the run snapshot included, matching §Snapshot's MUST.run-options.schema.json'stagsdescription now says what the prose says (MUST, not SHOULD; the constraints were already equal). The four scenarios tighten to the resolved answers:v2-run-cancelrequires409 run_terminal;v2-run-pause-resumerequiresrun_terminalon a terminal run andrun_state_conflict+details.runStatus: pausedon the second pause;v2-run-bulk-cancelrequiresid_tenant_mismatch/not_foundin the foreign entry;v2-run-annotation-not-eventrecords a 404/501 withfeedbackadvertised asblocked(not mounted), no longerinapplicable.
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.mdcarried 61 MUSTs and 12 witness sites, every one under §Create; five times any other core document).v2-run-cancel(terminal-run cancel; positive cancel onconformance-cancellablewithrun.cancelledwithin 5 s),v2-run-bulk-cancel(101 ids →400 validation_error+details.maxRunIds;[own, foreign, own]→ 200 in request order, the foreign entryok: falsein its entry and never a top-level 403),v2-run-pause-resume(409s on a terminal run; onconformance-delay: 202 paused / 409 / 202 running withrun.pausedthenrun.resumedin 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 matchingIf-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-failurediff MUST diverge — the control),v2-run-fork-refusals(replay + overlay → 400; badfromSeq→422 fork_point_invalid; a valid replay fork → 201 with the parent'sownerverbatim),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). Suite2.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 underevidence/v2-host-bundles/<host.name>.json; non-vacuous meanswitnessCount ≥ 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/openwop1.x,@openwop/openwop-conformance1.x,openwop-client1.x and the Go module v1.x MUST remain installable at their last 1.x version for 12 months from thev2.0.0tag, independent of v1 end-of-support.spec/v2/retention-floors.jsonpins 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.mjsreads the floor start from git (null until the cut), prints the state every run, and with--networkprobes each registry for the pinned version, failing a missing one while the floor is open. Coherence scenariov2-retention-floorsdrives it both ways with a canned probe and synthetic dates; its stem joins the bundle schema's coherence lookahead (packed content), so the suite is2.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 withwitnessCount ≥ 1), and the anchor: the commit at which a host's signed bundle, checked in underevidence/v2-host-bundles/, first became non-vacuous — read from the public history, never fromgeneratedAt, which nothing signs; a re-certification replaces the file and does not move it.scripts/generate-v1-eos-clock.mjscomputes both legs (every host's anchor + 90 days; 18 months fromv2.0.0iff an independent host was anchored at release) intoevidence/v1-end-of-support.jsonand always prints the clock state, because "not anchored" and "far away" would otherwise print the same nothing.check-removal-dates.mjsreads it: on or after the date, av1-end-of-supportrow'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 theopenwop.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;--checknames that when it fails. The corpus-coherence scenariov2-eos-clockdrives the script both ways; its id joins the bundle schema's coherence lookahead (packed content), so the suite is2.0.0-rc.46. docs/migration/v1-to-v2.mdcites 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 wascertifiable: false, the emitter'scertifiedflag never read the verdict (!notHeld && !rejected && blocked === 0), andwitnessCountread the v1 hand table and printed0for every v2 profile. Three more floor sites were unjoined besides:discoveryOnly/runtimeDerivedcame from the v1 table at major 2, soopenwop-discovery-corewas certifiable whatever its v2 floor file said; and the v1 table's prefix group (interrupt-) was still derived at major 2, where nointerrupt-file runs, so every major-2 bundle carried oneopenwop.floor.any.interrupt-row recordedblocked— 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;certifiedis the verdict; a profile certifies only with at least one witnessed pass on its floor (inapplicableis honest per file and cannot be the whole floor;skipped— an opt-in the host withheld — blocks);witnessCountis that number. The three floors are minted inspec/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-declarationrule 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--certifyexits 3; let it run and soft-skip. The cut gate (check-cut-gates.mjs) reads none of floors,certifiedorwitnessCount, so no bundle that passes it today fails it after this. 505 scenario files (v1 445, v2 61). Suite2.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%2For a backend that links to its own hostname overhttp; 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 the201returned, andeventsUrl/statusUrlMUST 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 asblockedon any non-200 (v2-id-grammar) — the honest word when a host will not let the suite look, the wrong word when the host answers404 No route matchesfor the id it minted seconds earlier. Measured 2026-09-05 on a tier-1 host: its public origin decodes%2Fto/before forwarding, so every tenant-bound id is unreachable through the front door while the direct service URL answers 200; the same create response linkseventsUrltohttp://<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 downgradehttps→http) — both real, neitherblocked. 504 scenario files (v1 445, v2 60). Suite2.0.0-rc.44.
payload_too_large(413) andunsupported_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-prefixedopenwop-app.payload_too_large(correct under the registry rule), the other a barepayload_too_large(a code claiming to be protocol). A size or media-type refusal on a core endpoint is as universal asvalidation_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.tsfetched/.well-known/openwoponce, with a 5 s abort, without the lane'sOpenWOP-Version, and on any failure set the fixture cache to empty — so all 311 fixture-gated sites recordedinapplicable, 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 recordsblockedwith 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 asopenwop.floor..** The ledger decided "is this file a floor?" fromPROFILE_FLOOR_SCENARIOS— the v1 hand table, which knows no v2 file — while--certifyderived the v2 floors fromspec/v2/profiles.jsonprivately. On a tier-1 host's first production bundle:openwop.scenario.v2-capabilities-root-closedexecuted-pass with 5 assertions, both claimed profileswitnessCount: 0over 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 mintsopenwop.floor.v2-capabilities-root-closedat major 2 andopenwop.scenario.…at major 1.check-declarationrule 9: a declared floor MUST name a scenario file that exists.
Added
v2-malformed-body-envelope—POST /runsandPOST /webhookswith body{under major 2 MUST be answered by the host:400, the JSON error envelope withvalidation_error, and theOpenWOP-Versionheader (§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-servedaccepted a hosting fallback's200 text/htmlas "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, noOpenWOP-Versionheader — 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 theOpenWOP-Versionresponse 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.tschanged 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 importcreateSyntheticSamlIdpfrom it, and a host that had mirrored the fixture's canonicalization instead of doing C14N began answeringbad-signaturefor 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 ofverify()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 theIssuerinside 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— everydata:frame on the major-2 run stream MUST carry the run's tenant-boundrunId, 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 thebatchflush) and the suite could not see it: of 56v2-*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.jsonsays which target majors a file is written for; the driver readsOPENWOP_TARGET_MAJORto 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 callv2Discovery(), 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.tsnow recordsinapplicable(with the reason, through the same journal entrybehaviorGatewrites) 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 proposedpinned: trueon eachnode.completedand 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.completedMUST carryoutputs, andv2-run-completed-outputswitnesses it. Both majors' payload schemas named the property and required nothing; v1 also left the object open. A tier-1 host emitted the singularoutputfor 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 mentionrun.completed; none read its payload.runCompleted.requiredis 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.jsonrequiredworkspaceId;identity.mdmakesowner.workspaceoptional. 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.workspaceIdis now optional and present exactly whenowner.workspaceis; §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
removalTriggercould not express what RFC 0176 §C.2 requires. The wrapper, the dotted mirror,Capabilities-Etagand the/.well-known/wopalias 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 carriedv1-end-of-supportalone whilev2-well-known-one-resourceasserted thev2.0-cutobligation 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-cutvalue — 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.mjsmatched/Id$/;runIds,sourceRunIds,contributingRunIds,nodeIdsareIds.POST /runs:bulk-cancelinherited v1'sitems: {type: string}through the derived OpenAPI, so a v2 client sent the tenant-bound ids it had been handed and every one answerednot_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 eachIdsproperty'sitemsschema; four arrays bind to their kinds, four are triaged not-a-kind (nextWorkerIdsis 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-majordefault that ran v2 scenarios over v1 requests and produced three phantom host defects, the bundle that passed with every rowblocked, 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 everyapi/v2/openapi.yamlparameter and response body MUST$refits kind") was published and never checked. 34 violations, not the 7 a name-matching check would find: only 20 of 88Idproperties share a name with a kind, andchildRunIdsat as{type: string, minLength: 1}in the same file whereparentRunIdwas correctly bound — so a child run's identifier carried no tenant segment for the mandatory403 id_tenant_mismatchrefusal to read.nodeStarted.typeIdhad no pattern at all. Coverage is now explicit: everyIdproperty is either bound to a kind or declared not-a-kind with a reason, and an untriaged field fails.
Fixed
- The
typeIdkind rejected ids that a legal pack name generates.node-pack-manifest.schema.json'snameadmits_; the kind did not, and a pack's node type ids are derived from its name — sovendor.acme.my_toolscould not declarevendor.acme.my_tools.echo. The kind now admits_. A scan of 539 distincttypeIdvalues 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. typeIdwas the only kind with no length bound. Every other$defbounds 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 localmaxLength: 256rather 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 NESTEDevent.runIdwas bound all along viarun-event.schema.json. A host that projectedrunIdon 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, sov2-dual-stack-negotiationasserted byte-equality with the v1 id whileidentity.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 mandatory403 id_tenant_mismatchrefusal to read, so a legacy unprefixed form would exempt exactly the v1-carried ids from major 2's tenant isolation.ids.schema.jsongets no legacy branch. api/v2/openapi.yamlcomponents/parameters/RunIdcould not express a conforming runId. Typed inline asmaxLength: 128; the tenant-bound grammar reaches 257 characters. Now$refsids.schema.json#/$defs/runId, whichidentity.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-negotiationasserted the v2 cross-major read returns arunIdbyte-identical to the one v1 minted, citingversioning.md§5. §5 says no such thing — it describes the scenario's shape ("creates one run through/v1/runs… reads it through/runswithOpenWOP-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 arunIdis tenant-bound<tenantId>/<opaque>, a grammar inids.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:engineVersionwas typed"string"whileversion-negotiation.md§Stamping says "Every persisted run document MUST carry anengineVersion: numberfield" — 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.jsonalready called the axis "a number, not a feature" andschemas/v2types itinteger(RFC 0172 §B axis 3): prose, register and v2 agreed, and only this file dissented. Corrected tonumber, 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 carrying0or1is contemplated by the contract and MUST still validate; raising it to2in v1 would make the legacy rule unrepresentable. v2 usesminimum: 2correctly, because the v2 era key admits only 2 and 3. (2) Neither field is inrequired— but §Stamping grantsengineVersionan 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 — absentengineVersionreads "compatible" and is benign; absenteventLogSchemaVersionreads 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 thatengineVersion"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:eventLogSchemaVersionandengineVersionare witnessed byera-key-stamped-v1and both are run-documentMUSTs; per-eventschemaVersionandpinnedVersionsare not asserted and carry noMUSTin 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 requiresengineVersionon 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 mentionengineVersion, and none asserts it on a run —version-fold.test.tsreads it from the discovery document andwasm-pack-load.test.tscarries it only as a type field. That is the same trap as theeventLogSchemaVersiondocstring one candidate earlier: the identifier is present, the requirement is not witnessed, and searching for the name answers the wrong question. Third leg added toera-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
MUSTthat no scenario has ever asserted, and a docstring that claimed it did.version-negotiation.md§Stamping: "Every persisted run document MUST carry aneventLogSchemaVersion: numberfield. The current v1 value is2." 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.tsopened by claiming it checks "the four version axes (engineVersion,eventLogSchemaVersion, per-eventschemaVersion,pinnedVersions) appear where the spec says they should" — andprotocolVersionwas the only axis asserted. Across all 444 v1 scenario files the sole occurrence of the identifiereventLogSchemaVersionwas that sentence: a docstring describing a check that did not exist.run-snapshot.schema.jsonrequires onlyrunId/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 witnessera-key-stamped-v1(2 legs, unaided, major 1) and the docstring now states what it actually asserts and namesengineVersion, per-eventschemaVersionandpinnedVersionsas 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/runspath, got a404, 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-runpassing while witnessing nothing: both requirement rowsblocked. Two causes — the scenario is seam-gated and the host advertises no seams profile, and the host's bundle is--target-major 1while 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/seamssurface 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 inGOVERNANCE.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, andhost.buildkeeps 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 ownexecutedFail: 0: the danger of a green bundle is not that it lies, it is that its scope is invisible in the number —executedFail: 0andinapplicable: 206are 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
devDependencythe 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 orlatestReadyRevisionName— 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 — alatestsecret reference resolves once at revision start. (3) A requirement that lives only in a JSON Schemadescriptionhas no witness and no MUST — the eventsequencefield 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 acceptedafterSequenceand discarded it, returning200with 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
Activewas prepared — and never opened as a PR, so it never landed. The RFC therefore stood atAcceptedon 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 revision00691-hayservesprotocolVersions ["1.1","2.0"],preferredVersion 1.1,signingKeys[]andeventLogSchemaVersion 3live; its signed bundle v3 (witness9420baa1ae9a, 1971 rows / 5741 assertions, signature resolving in the live discovery document) records the three gated scenarios at 34 / 30 / 20 assertions, allexecuted-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}.surfaceand 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.2satisfied every host gate for a corpus atrc.15) by requiringsuite.versionto equal the corpus being cut. That was too strict in a way a tier-2 host measured: between itsrc.8andrc.16bundles, the number of scenario rows added at--target-major 1was zero — every candidate in that span added onlyv2-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 attargetMajor N, every scenario filescenario-majors.jsonmarks applicable at N must have a row in the bundle. The original guarantee is kept in full — verified as a negative control, therc.2bundle still fails againstrc.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 againstrc.18an 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-servedcitingversioning.md §5. §5 says a host MUST advertise both majors, MUST emit the response header, and MUST serve/.well-known/openwopas 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 lone404cannot 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 advertisesprotocolVersions: ["1.1","2.0"]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-/v1surface a blanket rewrite would shadow, which is sound — and the list was simply incomplete;POST /webhooksunder major 2 returned404whilePOST /v1/webhooksreturned201. 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. Witnessv2-advertised-path-space-served(unaided): for each parameterless GET inspec/v2/path-manifest.json, probe/v1<path>and<path>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 lone404cannot separate. Verified both ways: passes thev2-referencehost, 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.mjsexists to answer "is every §F predicate machine-true on the release candidate" — and it never compared the host bundle'ssuite.versionto the corpus being cut. Measured 2026-09-04: a bundle at2.0.0-rc.2passed every host gate for a corpus at2.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 cleanexecutedFail: 0said nothing whatever about them. The gate now requiressuite.versionto equal the version being cut and says why on mismatch. Verified both ways: therc.2bundle now FAILS the Front door, and a freshly re-cut bundle passes — 150executed-passwhere 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.tsalready 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 withOPENWOP_OPTED_OUT_PROFILESfor deliberate non-implementation. Surviving reds get a named-scenario baseline that may only shrink — the ratchet shape this corpus already uses fordocs/witness-baseline.jsonand 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 thev2-referencehost 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 advertisedfixtures[]are a subset ofconformance/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, andv2-approver-enforcedsays so in its own docstring — it needs an approval fixture whoseapproversListnames a principal the suite is not, and recordsblockednaming it because "no such fixture ships inconformance/fixtures/". So the leg failed a host for doing exactly what the corpus asks. Found by running the suite against thev2-referencehost, 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-vocabularynow reportsblockedwhenseedEra2Logreturns 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 —
@nextcan name an exact-peer pair that was never published together, and the refusal said the wrong thing about it. The suite and@openwop/spec-artifactsare declared EXACT peers, published by two jobs in one workflow — so thenextdist-tag moves per package, and for the minutes between them@nextresolves a mismatched pair (measured live: spec-artifacts atrc.11beside conformance atrc.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-versionis 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 ofcmd; 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 — a200that honored the header versus one that ignored it; aninapplicablethat 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 1emits 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 toschemas/capabilities.schema.jsonas an OPTIONAL v1.x additive root key, the same shape RFC 0179 used for rootpreferredVersion; 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 isadditionalProperties: 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$commentsaid "RunEventDoc is a SERVER-EMITTED shape, so it is open (additionalProperties: true)" while the file setfalse— v1 text carried into a v2 file. A host that believes the comment emits root-level extension fields and failsv2-event-type-closed; the keyword is the contract and a comment is only a claim about it. (2)run-snapshot.schema.jsoncarried 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 atrun-event.schema.json#$defs.RunEventType— nothing$refed it, butrun-event-payloads.schema.jsonstill 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 againstevent-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 v2RunSnapshothad no seat for eleven fields the v1 wire actually carried. The v1 schema wasadditionalProperties: true, so it never had to declare what hosts served; closing the object in v2 made the omission visible as data loss — a fork'sparentRunIdand a run'sinputsbecame unobservable fromgetRun. Both are now declared, and anextensionsseat 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/tenantIdcould not express an anonymous tenant — it excluded:while RFC 0132 anon tenants areanon:<hash>and RFC 0170 §A.5 makes the anonymous surface first-class, so a conforming host could serve an anonymous run with neither a validowner.tenantnor a valid tenant-boundrunId. Ruled: admitanon:and onlyanon:(: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 nopattern, so a conforming v1 token may be a single opaque row key and such a host had no drain rule at all. Restated over theow2.prefix. (7)v2-interrupt-token-schemewas narrower than the registry it cites: its 404 leg demanded exactlynot_foundwhilespec/v2/errors.jsonalso registersinterrupt_not_foundfor 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.mjstested the attestation withtypeof 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, requiredkeyId/alg/publicKey, optionalretiredAt— a retired key MUST stay listed, since dropping it silently invalidates every bundle it already signed), and the Front-door gate resolvessignature.keyIdthere 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. Witnessv2-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.jsonpinnedsuite.versionto 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 whosesuite.versionis2.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--certifyemits v3 by DEFAULT — the v2-reference host has been emitting schema-valid v3 bundles at2.0.0-rc.Nthroughout. 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-
3log, and a contradiction between two of the suite's own scenarios.persistence.mdwrote the era contract for a v2 reader of an era-2log and never said what the v1 representation of an era-3log 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 era3, 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-3stamp 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 era2as "legitimate through the overlap" whilev2-era-keyhard-requires3— 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-Versionis 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 with406 protocol_version_unsupported. The third thing it can do — return200with the v1 document, ignoring the header — is invisible to every presence-gated scenario in the suite, because an absent v2 shape recordsinapplicablewhether 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 sitAcceptedon a host serving none of it. Measured on the live tier-1 host 2026-09-04:OpenWOP-Version: 2returned200with a body byte-identical to the header-less fetch. Witnessv2-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. Witnessv2-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 era2, another writing nothing, and discovery advertising1— a value no path wrote — and asked three questions the corpus did not answer. Ruled: (1) absent stays era2forever and is never backfilled; the trichotomy is sound only because a v2 host stamps3on 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 era2— 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. Witnessv2-era-stamp-universal(2 legs, unaided). - v2 charter Phase 4, P4-SPEC-1 — the era-
2writer rule (persistence.md, no wire change). The reader rule translates an era-2log 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-2log 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 withevent_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.1release candidate. RFCs 0169 (discovery and capabilities), 0174 (governance) and 0178 (assurance registers and deprecation machinery) flipActive → Acceptedon 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 stayActive.scripts/check-cut-gates.mjsrenders 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 reportblockedwithout one. The Coexistence gate passes on thev2-referenceexample host — a host implemented fromspec/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). 51v2-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,afterSequencepoll 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 15src/coherence/v2-corpus wrappers, every assertion areq()id;scenario-majors.json494 files (v1 444 / v2 51); the two RFC 0176 seams (seedEra2EventLog,receiveWebhookDelivery) enterapi/seams-v2.yamlviaderive-v2-api.py; 9SECURITY/invariants.yamlrows (7 protocol-tier with v2 witnesses, 2 demotedreference-impl/unwitnessableper RFC 0170 §E.2 — witness baseline 19 → 21, recorded); requirement ids appended to the falsifiability rows of every child;evidence/corpus-ledger.jsonregenerated at2.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):witnessDigestover the reporter record, Ed25519signBundleV3/verifierSignover the canonical JSON of{witnessSha256, host.build, suite.version, discovery.sha256}, andverifyBundleV3that recomputes the digest, the totals andassertionCount, requiresdetailfor any non-pass,host.build, a well-formed signature (verified when the host public key is given), refuses anindependentclaim 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 whenblocked > 0.--certifyemits v3 by default (--bundle-version 2deprecated; v1 gone per §E.3) and needs--host-build <kind>:<id>,--signing-key <pem>,--signing-key-id(or theOPENWOP_HOST_BUILD/OPENWOP_BUNDLE_SIGNING_KEY[_ID]env),--evidence-tier self|steward|independent(independent adds--verifier-key/--verifier-key-id), recordsOPENWOP_HOST_RELAXATIONSintohost.relaxations[], embeds the spec-artifacts lock intosuite, self-verifies with the derived public key and validates againstschemas/v2/certification-bundle.schema.json(which gainsverifierSignature, required when a claim is independent).lib/seams.ts(RFC 0168 §C): under--target-major 2the 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 sendsOpenWOP-Version: 2.0on every request unless the scenario set one (RFC 0172 §A.3); the seams profile is advertised through theconformanceMETADATA 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 onrelease/1.x). (1)req()is the only assertion form (§A.1): everydriver.describe(in the 444 scenario files becamereq('<id>', section, requirement)with the id the per-itledger already derived, so no bundle id changes; the 23 interpolated titles carry hand-minted ids;req()now returns the same[impl@version] section: requirementmessage;scripts/check-req-only.mjs(stage 1) fails on adriver.describe, an un-id'd assertion message, a barereturnin anitbody (the RFC 0148 G8 unclassified return — the ones found were classified), or a duplicated explicit id;conformance/requirements.jsonreportsexplicitIds= 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 byscripts/generate-spec-artifacts.mjs --write/--check(225 files); the suite declares it as an exact-pinned peer, no longer vendorsapi//schemas/, embedsdist/spec-artifacts.lock.jsonat 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 onlyschemas/CORPUS-STAMP.jsonfor 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) andopenwop-publish.yml(apublish-spec-artifactsjob onv/openwop-spec-artifacts/vtags; pre-releases publish under the npm dist-tagnext) 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-validboth; the rest 1); the CLI resolves the default from the host'spreferredVersion(RFC 0179), elsemax(protocolVersions[]), else 1, passes the selected files to vitest, setsOPENWOP_TARGET_MAJOR, and now honors--filterunder--certify;targetConfigurationSha256hashes the resolved base URL (it hashedundefinedwhen the URL came from the environment). (4) Corpus-coherence out of host bundles (§D.1): the 29 scenarios moved toconformance/src/coherence/— the set is the directory listing (four hand-kept copies retired: thespec-coherence.tslist, thepackage.jsonnegations,spec-coherence-scenarios.json, the registry self-test's list);scripts/check-spec-coherence.mjsruns them undervitest.coherence.config.tswith the per-itledger and emitsevidence/corpus-ledger.json, the "corpus gate — no host tier" evidencecheck-accepted-predicate.mjsreads; the suite self-tests run undervitest.selftest.config.ts; a host run never executes either. (5) Release lines:release/1.xcut frommainat7c81ace8; the spec and PR-check workflows run onrelease/**;openwop-publish.ymlrefuses a tag on neithermainnorrelease/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 -won raw markdown, generatedheaders.mdincluded):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.mjsreads them),identity(0170),idempotency,runs,events,errors(the 92-code table generated fromspec/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 inscratchpad-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 nowspec/v2/errors.jsonrows;negotiation.decidedis a decided codemap row with a closed payload;interruptResolvedcarriesresolvedBy/decisionandrunPausedcarriesdrainPolicyper the v1 MUSTs; bundle v3 durability issession | deployment | persistedand the effect-seam manifest row is{ seam, kind, guarded, guardedBy, branchReFires? }exactly as RFC 0173 §A.2/§C.1 state them; replaymodesisreplay | branch).spec/v2/ext/: the 13 ext-anchored families withwitness:/technical:/adoption:headers,grpc-transport/(demoted; non-normative proto),portability/(goals/export/import decided out ofapi/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) andthreat-model-replay.md§6–§8 (RFC 0173 §E.2);check-threat-model-template.mjsnow strict in stage 10. RFC 0174 §E.3: the 43spec/v1/*.md"Open spec gaps" tables (196 rows: 132 id rows, 42 id-less table rows, 22 bullets) arespec/v1/spec-gaps.json, hand-classified (open 107 · closed 69 · carried 20; witness 0 unclassified), merged intospec/v1/gaps.jsonbygenerate-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-baselineno longer drops other scripts' ratchets (selfCarriedrestored 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) derivesapi/v2/openapi.yaml(51 operations on unversioned keys over a bare origin — RFC 0172 §A.2; every operation takesOpenWOP-Versionand every response declares it — §A.3/§A.4;X-headers renamed toOpenWOP-andCapabilities-Etaggone — RFC 0171 §C.1; the poll cursor isafterSequencewith omission = from the first event and the one-meaning response shape — §E.2; the interrupt token path parameter carries theow2.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 surfacesGET /host/effect-seams,GET /runs/{runId}/compensation,GET /runs/{runId}/effectswith newcompensation-projectionandeffect-ledger-projectionschemas;GET /host/eventsas the documented default hostEvents address),api/v2/asyncapi.yaml(server pathname empty, onerunEventschannel whose address is the OpenAPI key withstreamModeas a pattern over the closed set,hostEventswith 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/…) andspec/v2/path-manifest.json(operations + channels).spec/v2/release.jsonis the one release identityinfo.versionreads (RFC 0172 §D.1). Stage 10 gains the derivation--check, both Redocly lints (api/v2/redocly.yamlkeeps the two v1 suppressions with the reason recorded against RFC 0178's acceptance), the AsyncAPI validation and the v2 leg ofcheck-path-parity.mjs(43 keys, proto leg retired-by-0175). Editorial forward pointers on the three RFC 0172 retractions inrest-endpoints.md§Versioning andgrpc-transport.md(the v1 text stands;api/asyncapi.yaml'spathname: /v1is 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.mjsseeds 85 schemas from the v1$refclosure ($idunder/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$refids.schema.json,engineVersioninteger, 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 withminted),subject.schema.jsonv2 (lanessession/anonymous,kind ⇔ lanefor anonymous,keyClassiff SAML/SCIM, the actor chain bounded at four by$defs),subject-link.schema.json,run-snapshot(owner.subjectrequired,eventLogSchemaVersionrequired ≥ 2,principalgone),run-event(typeoneOfclosed enum ⊕ positive vendor pattern,schemaVersionrequired),run-event-payloads(root and every definition closed;_typeIndexnormative and generated fromspec/v2/event-codemap.json— all 117 rowsdecided; the seven legacy interrupt payloads are$refs to the unifiedinterruptRequested/interruptResolvedper §A.4 E4),spec/v2/errors.json(82 codes: the 40 ofrest-endpoints.md, the OpenAPI one-member enums, the pack and connection codes, and the 19 minted by RFCs 0169–0177 withstatusSourcerecorded) → generatederror-envelope.schema.json(scripts/generate-error-envelope.mjs),configurable.schema.json(RFC 0171 §D.1 closed nested shape;run-optionsand the capabilities metadata$refit),certification-bundle.schema.jsonv3 (RFC 0168 §E: closed,witnessSha256/assertionCount/host.build/per-profile tier and count/signaturerequired,detailconditional,host.relaxations[]),effect-seam-manifest.schema.json(RFC 0173 §C), pack schemas (signing.schemeconst,keyIdonly,kindrequired, engines grammar with an explicit ceiling,versionDeprecated),debug-bundleseqgone (C4.7), andspec/v2/facets/*.schema.json— the hand-decided facet shapes fora2a,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, nosupportedon a family) andgenerate-error-envelope.mjs --check.
- v2 charter Phase 3, P3-A — foundations for
spec/v2/(in construction; suite1.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 atv2.0.0-rc.1with the in-memory example host asexecuted-passWitness/Coexistence evidence, openwop-app's legs at Phase 4; RC flip set {0169, 0174, 0178}): RFC 0179Active— rootpreferredVersion(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) andspec/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 asopenwop-check.shstage 10; the 1.x tarball never carries the v2 tree (pack-vendor.shprunesschemas/v2,api/v2,api/seams-v2.yamlbefore the digest walk);spec-corpus-validity's v1 leg skipsschemas/v2/;generate-cross-repo-evidence.mjsrecords the registry's distinctpeerDependencieskeys (the committed inventorycheck-declaration.mjsreads instead of a sibling checkout); PROTOCOL-STATUS lists the 18 version axes incl. OpenAPI/AsyncAPIinfo.version;advisorytier documented inSECURITY/invariants.yaml's header; witness baselineunwitnessable20 → 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 toCORPUS_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-itledger and a reword-without-alias CI failure (the Phase 1 helper exists withexplicitIds: 0and an empty alias file); the witness verdict is required on every family, requirement and invariant,unwitnessableneeds a rationale and fails on a protocol-tier row (55unwitnessableand 37tests: []measured); the six things v1 admits it cannot certify each map to one class andblockedretires as a bundle disposition; a seam-gated MUST mints an observation path or is demoted;host-sample-test-seams.mdbecomes the profileopenwop-conformance-seams-v2with its ownapi/seams-v2.yaml, forbidden from the capability namespace (its twotestSeamsflag 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 publishesdist/fixtures/vectorsonly 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-majorand 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 withindependentrequiring a verifier key distinct from the host's (RFC 0148 G4/R5, RFC 0154 G4). Row C1.1 re-kindedretype → behavior(a codemod would fabricate evidence). v1.x fix: the CLI's--bundle-versionhelp said "Default 1" while the code sets2(packed content: suite1.161.0 → 1.162.0). Phase 2 exit: all eleven RFC 0167 childrenActive;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 oneventLogSchemaVersionwith absent ⇒2(the tier-1 host persists no such column) and v2 writing3, sequence space preserved including0, unmapped types refused (event_type_unmapped) rather than tolerated; the adapter's seat is the storage boundary (openwop-appstorage.listEvents, whose wrapper 34 call sites bypass; MyndHyvefromFirestore); a v1-pinned run continues under the adapter or is cancelledv1_pin_unsupported/cancelledBy: v2-cutover(V5 closed; MyndHyve'sLEGACY_RUN_CANCELLEDis the precedent);/.well-known/openwopis one resource whose representation the RFC 0172 header selects (the charter's per-major sub-objects deviation recorded), withremovalTrigger(new optional register field) on the wrapper, the dotted mirror,Capabilities-Etagand 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_unsupportedbinds at install on every publication path (the mirror included), andregistry/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 onesigning.scheme;publicKeyRefand absentkindgo; the RFC 0138 hatch lands onagent-manifestandprompt-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 andi18n.md(RFC 0137 G1/G3). RFC 0170's persisted-data row corrected (openwop-app has nometadata.owner). 27 migration rows; fiveproposedregister 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.supportedand 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 toext/at the cut; RFC 0035 is superseded by thepacksobligation at the cut; the replay threat model gets §6–§8. C.8 demotes gRPC toext/(no client, no advertiser, a proto that calls itself abridged), deletessupportedTransports, 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-freenegotiation.decidedevent, caps MRTR rounds, decides RFC 0100 UQ4, and writesSECURITY/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 withcompensation.mdG8;extensions.json's compensation advertiser note corrected;grpc-transport.md:202's "RFC 0094 (in flight)" corrected. 17 migration rows; twoproposedregister 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 andowner.principalgoes; lanes gainsessionandanonymous(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;SubjectLinkgets 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-Keygets a grammar and a 128-bit floor; interrupt tokens carryow2.<alg>.<kid>(the v1 rotation rule atinterrupt.md:397references a discriminator the format at:389does not contain); RFC 0164 G4 decided (no profile fold); RFC 0159 G3 confirmed closed. C.4: closed eventtypewith a positive vendor pattern and the 20 codemap decisions recorded asv2Override(0 review rows remain;runOrchestrator→orchestrator, fourcore.types fold, artifact events keep noun names); every payload definition closed; the closed-enum growth rule;errors.json;OpenWOP-for every header; closed nestedconfigurable; one events channel;afterSequencewith omission meaning from-the-start (RFC 0165 G7 decided; the first event stays 0) and the poll response shape, which disagreed betweenversion-negotiation.mdand OpenAPI with no register row, unified. Three codemods (event-type-codemapdriven byevent-codemap.json,debug-bundle-seq,configurable-v2); 26 new migration rows; threeproposedregister 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 inRFCS/root move underregisters/; 45 gap rows whose prose said CLOSED while their token carried themselves are re-tokenedclosed(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 — andfrontend-plugin-packs' RFC status word); two stale gap rows close; RFC 0155's repealed-budget residue closes; new gatescheck-rfc-status-coherence.mjsandcheck-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 landscheck-falsifiability.mjs(the RFC 0167 family's tables parse with closed verdicts) andcheck-gap-contradictions.mjs(first catch:artifact-type-packs.mdsaid a test did not exist that does), every deprecationkindnow needs a detector,schemas/README.md's five stale RFC-status words are corrected and checked,api/redocly.yamlcites the right gate step, and twoproposedrows enter the register (gap-local-alias,manifest-deprecated-field— the pack-manifestdeprecatedwire 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.yamlandschemas/README.mdare 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 anexternally-gateddisposition; ten migration rows and theopenwop.codemod.discovery-document-v2codemod (refuses rather than guesses on a disagreeing mirror, a baresupportedwith no versions, or an extension family with no vendor). C.5: major negotiation byprotocolVersions[]+ rootpreferredVersion+OpenWOP-Versionon a bare origin with unversioned v2 path keys (no/v2/path space), all 18 axes dispositioned,engineVersionunified withopenwop.codemod.engine-version-unify,schemas/v2/under/spec/v2/, one release identity; nine migration rows; the/v1/path prefix enters the deprecation register asproposed(27 rows, 29 detectors). Both carry a recorded adversarial review (eleven findings absorbed, including thatrest-endpoints.mdbullet 3's400on unversioned roots inverts in v2 and that MyndHyve's undeclared rootprofiles[]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.mjschecks 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 codemodopenwop.codemod.capabilities-wrapper-removal), 11 newdeprecations.jsonrows (26 total:since, theX-request-header family,replay.fork, debug-bundleseq,credentialProvider,publicKeyRef, the legacy interrupt payloads,a2a-0.3-legacyandmcp-2025-06-18-legacywith 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-rebrandwop-agents-*example strings, anddebug-bundle.schema.json'sbundleVersionpattern, 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) advertiseprotocolVersions, persistowner.subjecton new runs and synthesize theurn:openwop:legacysubject on old ones, copy tenant + subject onto forks, dual-emit theOpenWOP-*webhook headers and serve a standardETag/304on 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 narrowsauth.profilestoopenwop-auth-saml— the RFC 0164 erratum remediation — andINTEROP-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-echoreads the first event without a cursor.lastSequence=Nmeans "I have seen N" and the first event is sequence 0, solastSequence=0asks a conforming host to skiprun.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+1that droppedrun.startedeven 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. Newhost-sample-test-seams.md§25 (POST /v1/host/sample/test/idempotency/concurrent-claim) +idempotency-concurrent-claim.test.ts;layer2-invocation-claim-atomicmoves reference-impl → protocol tier with a real test. **The seam reports thelogicalInvocationIdeach executor minted and the scenario asserts they are identical before assertingdelivered === 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 recordsblocked(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 firstawait, 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 usedINSERT OR REPLACE— the precise inverse of a claim, since it always wins and never reports a conflict. Suite1.157.0 → 1.158.0; also syncsconformance/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.tsasserted thatgenerate-assurance-status.mjscontainsconst 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 intoregisters-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 viaparseRegister/row.token, the legacy free-text path keepsconst negatedand itsclosedMark && !negatedrequirement, 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 asubject.schema.jsoninstance 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|withdrawnand 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 — andgenerate-assurance-status.mjsderived "reviews completed" from a free-text match,/retrospective review (complete|closed|done)/iover 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 waswithdrawnorcorrective-rfc-requiredexactly like aratifiedone, reporting compliance over an open defect. §B names four outcomes precisely so they are not interchangeable. Newdocs/WAIVER-RETROSPECTIVE-REGISTER.md(45 rows, allnot-reviewed) +scripts/check-waiver-retrospective.mjs, wired intoopenwop:check: rows are derived and held in agreement with the tree, outcomes must come from the closed vocabulary, onlyratifieddischarges, and a non-not-reviewedoutcome must name a reviewer org — an unattributedratifiedis the exact silence-means-ratified substitution §B's last clause forbids, and all four controls were verified by sabotage.not-reviewedis 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.mdlists one maintainer, and the non-steward tripwire has not fired — so a steward self-review recorded asratifiedwould 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-echono longer races the host's asynchronous start. The §B.2 echo leg read the event log immediately afterPOST /v1/runsand failed a conforming host whose executor appendsrun.startedon the next tick (found on the openwop-app RFC 0165 leg:run.started MUST be presentwith the run stillpending). The leg now waits for the run to leavependingbefore 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 inopenwop:check), plus the §C.2 honesty clause: 25SECURITY/invariants.yamlentries and 6spec/v1/extensions.jsonrecords were re-judged against what their cited scenarios can observe; 10 movedwitnessable-gated → seam-gated(the fiveanon-actor-invariants andopenwop-anonymous-actorprobe only/v1/host/sample/anon-surface/;openwop-workload-identity-v1,openwop-a2a-1-0,openwop-mcp-2026-07-28,openwop-compensation-v1need the §20/§21 seams and recordblockedwithout them), 21 confirmed.docs/witness-baseline.jsonratchets 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-samlandopenwop-auth-scim; the RFC 0165 host-leg discovery found MyndHyve's live/.well-known/openwopadvertising both with nosubjectLinking(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 counterfactualsafety-fixclassification recorded in RFC 0164 §Compatibility is the one that applied, and the remediation is the RFC 0165 MyndHyve leg droppingopenwop-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(schemaevent-codemap.schema.json,scripts/generate-event-codemap.mjs --check) carries one row perRunEventType— 117 — with its payload$defand a MECHANICAL proposed v2 name under the charter'sdomain.verb-edrule (18 rows flaggedreview: truefor 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 inSECURITY/invariants.yamlandconformance/coverage.mdresolved 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-egresscited test files that never existed at that path); both now point at openwop-app'sa2ui-render-invariants.test.tsx, which carries both probes by name. New corpus-coherence scenarioevent-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 byscripts/backfill-registers.mjs(367 by the README rule that an unmarked row on an Accepted RFC is the named carried-forward gap) and gated byscripts/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, idsopenwop.gap.<rfc>.<n>, schemagaps.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 stampedwitnessReview: initial-mechanical-2026-09-02, with three ratchets indocs/witness-baseline.json(check-witness-classes.mjs); 19tests: []invariants gain a rationale.generate-assurance-status.mjsreads 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 gainsRejected;docs/RETROSPECTIVE-QUEUE.mdpublishes the RFC 0147 high-risk cohort (0148, 0150, 0152, 0153, 0154) asprovisional; RFC 0155 §D's uncalibrated 12/4 budget is repealed in favour of the witness requirement.protocolVersionsclassified 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) rootprotocolVersions: string[]beside the scalar, strictMAJOR.MINORgrammar, MUST containprotocolVersion, not consulted by profile derivation; the splitengineVersiontype is recorded, not changed. (2)owner.subject— newschemas/subject.schema.json{issuer, subjectId, tenant, lane, kind, keyClass?, actor?}as a declared optional member of both closedownerobjects (therun.startedecho also gains theprincipalKindit 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 theOpenWOP-webhook header family and a standardETagon discovery;X-openwop-andCapabilities-Etagenterdeprecations.jsonasdeprecatedwith 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 → Acceptedgates 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/libself-test are excluded from the package (conformance/spec-coherence-scenarios.jsonlists them; the pack-contents gate fails if they return);schemas/CORPUS-STAMP.jsonnow carries a SHA-256 per vendored file and the suite refuses to run in the published layout when any digest mismatches;--offlineis declared as exactlyfixtures-valid.test.ts. Layout-gate floors re-measured at 1,350 tests / 300 files.
- v2 charter Phase 1, tranche 1 — per-
itrequirement 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,--certifyemits them, and a generatedconformance/requirements.jsonmaps every id to its citations with an alias file gated inopenwop:check. This is the durable fix forconformance-certification.mdgap 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 (everyINTEROP-MATRIX.mdhost 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.mdgains §"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 atsite/src/build.mjs, which moved toopenwop-sitein June, and covered only the site; it now sequences all nine repositories. (4)spec/v1/deprecations.json— the deprecation registerCOMPATIBILITY.md§7 now points at: nine RFC- or annotation-backed deprecations (capabilitieswrapper,host.*dotted mirror,openwop-core, channel prefix conventions, baresupported: truein A2A and MCP,config.outputArtifactType,config.chatCard,capabilities.auth.subjectLinking, bundle v1) with authority, replacement andremoveIn, plus fourstatus: "proposed"rows the v2 charter proposes but no RFC has made (Capabilities-Etag,contractProvenance, the un-prefixed webhook signature form, the409/410interrupt-token overlap). Schema atspec/v1/deprecations.schema.json;scripts/check-deprecations.mjs(unique ids, schema-valid, every cited source file still contains the surface token,removeInis a v2 version) runs innpm 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 inapi/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 anAcceptedRFC — closed. (6)--certifydefaults 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 bothopenwop-auth-samlandopenwop-auth-scimonly 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 0163unboundcarve-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-scimdropped; aligned + no seat ⇒ dropped; aligned + seat ⇒ both profiles +subjectLinking: true+subjectLinkKey; aligned + seat + unbound ⇒ SAML401 subject_link_unbound; single-profile + unbound ⇒ RFC 0159 deny-only survives) plussaml_assertion_unbound_refusedinauth-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 --noEmitclean, auth suites 25/25,npm run cigreen (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 atAcceptedstill finds no production host advertising both profiles. Accepted 156 · Active 4 · Draft 1.
tenantIdonPOST /v1/webhooksand RFC 0048'sowner.tenantwere 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 definestenantas something else — the top-level isolation boundary — withworkspacean optional sub-tenant beneath it. On the common host where tenant ≡ workspace the two coincide, which is whydiscoverOwnedTenant()readingowner.tenantworked everywhere it had been run. Reported 2026-09-02 by a tier-2 host with a real workspace layer, whose schema-correctowneris{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. Suite1.149.0 → 1.150.0prefersowner.workspace.webhooks.mdv1.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 itstenantlabel 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) emitscapabilities.auth.subjectLinkKey: "opaque-idp"from the same gate assubjectLinking, reconstructs the fixture's signed<saml:Issuer>in its validator and surfaces it on the principal, binds the SCIM connection to an IdP entityID (idpUrlaccepted 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 — advertisementsubjectLinkKey === 'opaque-idp'; same-root provision + assertion authenticates as the positive control; cross-root collision refused401 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) andtest/auth-saml.test.ts(signed<saml:Issuer>in the canonical +principal.issuersurfaced).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 returns200 authenticated:trueand 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 advertisessubjectLinking.INTEROP-MATRIX.mdgains the subject-linking sub-table;coverage.mdbehaviour 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.subjectLinkKeynames classes, not vendor attributes:{opaque-idp, configured-immutable}replaces theDraft'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) ThesubjectLinking:true ⇒ subjectLinkKeyco-requirement is a schemaif/thenas well as a prose MUST; the prose-onlyDraftleft 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 noauthblock, 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 inMAINTAINERS.md.Active → Acceptedgates on the reference host emittingsubjectLinkKeyand enforcing the same-IdP check. Suite1.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 therun_forbiddenMUST; 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" shapedocs/EVIDENCE-DISCIPLINE.mdpractice 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 usedINSERT 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 invariantlayer2-invocation-claim-atomic(185 total) plusSECURITY/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 emptytestslist, 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'sdurability/duplicate-deliveryrow would cover it, but 5 of that RFC's 6 conformance rows have no scenario at all (onlypoison-exhaustiondoes), andduplicate-deliveryis named in no rung's required evidence —durable-single-instancerequires onlykill-after-acceptandkill-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 releasecheck was designed on the assumption that a pinned-then-superseded version was an anomaly worth naming —1.143.0was 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:checkstep 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; and1.143.0was 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. Newscripts/check-pending-suite-release.mjs+ a scheduledPending suite releaseworkflow 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 inopenwop: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/openwopexactly as the first cohort: 32 of 32 unadvertised, 0 advertised-but-failing. Worth recording thatworkflowChainPacks.supportedandworkflowChainPacks.subChains.supportedboth needed entries despiteworkflowChainPacksalready being listed — opt-out matching is exact-string, not prefix, so every gated sub-key needs naming..github/workflows/conformance-soak.yml.
Conformance Soakhad 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 wasECONNREFUSED 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'sStatementdestructor (RemoveEnvironmentCleanupHook … Assertion failed: (env) != nullptr) — v11 declares noengines.nodeat 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^12not^13because v13's>=22would invalidate the host's own>=20engine 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/openwoprather than by pattern-matching the failure text: all 13 resolve toundefinedat the document root, undercapabilities., and inprofiles[].dispatch.perItemInputis the instructive one —dispatchis 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 bothopenwop-auth-samlandopenwop-auth-scim, omit one boolean, and ship the leaver bypass while the suite recordedinapplicable. RFC 0164 (Active) makes the obligation follow the profile pair: advertising both implies RFC 0159 §A and RFC 0163 §A/§B;subjectLinkingbecomes a derived advertisement that MUST betrue(schema conditional onprofiles[], composed with the RFC 0163 conditional underallOf) 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. Classifiedadditive, 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 noauthblock; 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-advertisednew);version-negotiation.mdgains the combined-host migration section; waiver recorded inMAINTAINERS.md.Active → Acceptedgates on the reference host narrowing its advertisement when its lanes' realms are misaligned. Suite1.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-incapabilities.auth.subjectLinking:true: (1) a new optionalcapabilities.auth.subjectLinkKey— a closed enum of allowed classes only (opaque-idp,oid,immutable-id) — that asubjectLinking:truehost MUST advertise and MUST honour, withemail/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 advertisingsubjectLinking:true; no host conforming to RFC 0159 or RFC 0050 today de-conforms. New optionalcapabilities.auth.subjectLinkKeyenum;auth-profiles.md§"Subject linking" gains the key-class declaration + the same-IdP MUST; RFC 0159 gains anAmended by → 0163pointer;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 scenarioauth-subject-link-key-class.test.ts(published tarball content changed → identity bump): the §A advertisement leg is executable + server-free (asubjectLinking:truehost MUST advertise asubjectLinkKeyin the closed enum); the §B same-IdP-trust-root behavioral leg is declared + phased (soft-skipsblockeduntil a two-trust-root fixture is engineered — RFC 0163 gap G-fixture), mirroring how RFC 0159 phased its cross-lane behavioral legs. Capability-gated onauth.subjectLinking.
- Suite
1.144.0 → 1.145.0. Newspec-coherence-registry.test.tsassertions pinning the packagedfileslist (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
inapplicableonly whenV1_DIRis null, which happens only in thepublishedlayout — andresolveLayout()keys offPKG_ROOT, not the consuming repo, so an npm consumer's parent is alwaysnode_modules/@openwop/, which never containsschemas/whatever the host's checkout path. Verified against a realnpm install:published,V1_DIRnull. 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_ROOTon finding a sibling checkout, and false of the artifact. The other read the layout code rather than assume, found thePKG_ROOTkeying, 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
specto the packagefiles— "ship the corpus so consumers can run those tests too" — would give npm consumers aV1_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 thatschemas/stays shipped, since that is what selects thepublishedbranch 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.mdfor 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 scenarioauth-subject-link.test.ts(published tarball content changed → identity bump), plus thecapabilities.auth.subjectLinkingschema property and theauth-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 viacapabilities.auth.subjectLinking: a host that sets the flag MUST link the SAML and SCIM principals of one human on an opaque, IdP-stable id (SCIMexternalId↔ persistent SAMLNameID), 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:forkreplay + RFC 0048 §D owner-echo). New optionalcapabilities.auth.subjectLinkingboolean;auth-profiles.md§"Subject linking (SAML ⟷ SCIM)"; new gated conformance scenarioauth-subject-link.test.ts. RFC 0050 gains anAmended by → 0159pointer. 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-successagent.toolReturnedMUST carry a discriminator —errorpopulated (_errorObject, andstatus: 'error'underprePostEvents), or a §C/§D gate status (forbidden/rate_limited) — and a return withoutcome/errorabsent andstatus∈ {absent,ok} MUST NOT represent a failure.error.codeis free-form, so execution errors, timeouts, and capability-precondition refusals (e.g.host_capability_disabled,durationMsabsent) are all nameable without a new enum member. Additive: mandates populating existing fields — no schema, error-code, event-type, or invariant change. Addstool-hooks-failure-honesty.test.ts+ the toolhooks seam'ssimulateToolErrorarm. 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.tsreturned 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_EXCLUDEDreplaces 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 theV1_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_URLfronts the suite's own in-process receiver, and a tunnel has to be aimed at a port the operator knows in advance; the receiver calledserver.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) differnamingwebhook-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
blockedin every host's certification bundle about a host they never examined. They readspec/v1/.mdto check the corpus is internally coherent — theprotocolVersiongrammar, error-envelope shape, normative-example extraction, RFC lifecycle.spec/v1/is deliberately not bundled in the published tarball, so theydescribe.skipIfat collection time, no test body runs, no reason is recorded, and RFC 0148 §A resolves an all-skipped file with no reason toblocked. A host operator readsblockedand 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 pointingOPENWOP_CONFORMANCE_ROOTat a spec checkout and watching 85 assertions pass in about a second, 59 of them against a deadlocalhost:9. - The fix is correct use of an existing disposition, not a new one. RFC 0148 §A defines
blockedas "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 inCERTIFIABLE, so these rows stop counting against a host that cannot affect them. A sixth disposition value was considered and rejected:certification-bundle-v2.schema.jsonenumerates five andverifyBundleV2is 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-aliasand four others gate onV1_DIRand drive the host. They assert advertised behaviour that a missing dependency prevented exercising — §A's definition ofblocked, verbatim. Calling theminapplicablewould 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_DIRand never callsdriver— sospec-coherence-registry.test.tsrecomputes it from source every run and names the file to add or drop. Without it a new corpus scenario would silently reportblockedin every host bundle forever, and one that grew adrivercall 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 intoinapplicable.
- 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.1on 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/openwopsaid v1.6.1 (npm serves 1.8.0, published this cycle),openwop-clientsaid v1.5.0 (PyPI serves 1.6.0), andopenwop-sdks/gosaid 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.mjsnow asserts the README's advertised conformance version againstconformance/package.json— a local fact, always checkable.scripts/check-advertised-versions.mjs(step 8 ofopenwop: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:UNKNOWNis reported as UNKNOWN, tolerated locally, and fatal underCI=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 aCHANGELOG.mdrelease 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
mainon every commit, and a CHANGELOG-only PR is what revealed it.schemas/CORPUS-STAMP.jsonembedscorpusCommit=git rev-parse HEADat 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:suiteVersionstill counts (a stamp claiming the wrong suite version is real drift and still fails),corpusCommitis 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 wrongsuiteVersionfails, and a one-line scenario edit fails.
1.10.0
2026-08-25the 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.1on npm was not the1.138.1in this tree. Fifteen files of drift — a newglobalSetup, seven scenarios,vitest.config.ts, the package README, the corpus stamp — had landed under one version number sincev1.9.0published it. A host pinning1.138.1and a reader of this repo were looking at different suites while both believed they were current. The version had stopped identifying anything. Bumped to1.139.0across all three pins; a minor rather than the patch the net-new-scenario rule implies, becauseglobal-setup.tsandforkDeclinedchange what every consumer's run does.- Nothing caught it because the publish guard asked whether the version EXISTED, not whether it was THIS one —
if 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. Newopenwop:checkstep 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,FAILnaming every file, andUNKNOWNwhen 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-maxtimeoutbelow 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 intoOK— 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 wheneverCI=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/inapplicablewith a reason;OPENWOP_LEDGER_PATH's only writer wascli.tsinside--certify, so a plainvitest runcomputed the disposition and discarded it at process exit, leavingN passedas the sole artifact. Two host implementers independently read that line as coverage. AglobalSetupnow 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 realLedgerEntrytype 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, noX-openwop-Event-Type) — the exact names the suite removed on 2026-08-09 when a tier-2 host emitted whatwebhooks.mdshows 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 inopenwop-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;
--certifyon a default-env boot recorded the scenarioblockedbecause the SSRF guard refuses the loopback receiver; the one bundle run that did execute the assertions wentexecuted-failand was filed "not diagnosed here"; and CI ran it red onmainevery 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-conformancenow 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_PRIVATEopens three gates and the contract named one. The loopback receiver is plainhttp://127.0.0.1, whichwebhooks.mdforbids 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-ssrfrequired 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 refusinghttp://10.0.0.5/pushwhile acceptinghttp://push.example.com/satisfied every written word and shipped a plaintext push channel. Classified W3C Process Class 3 inCOMPATIBILITY.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}:forkin order to say it had not. Eleven call sites handled501and none in the replay family handled404— the weaker signal treated as a skip, the stronger one as a defect. One rule now inconformance/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-suppressionwas 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-boundis now recorded as structurally un-witnessable rather than left for someone to discover. docs/EVIDENCE-DISCIPLINE.mdgains 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 advertisingcontextBudget, so it takes the deferral rather than claiming a witness. RFC 0121 staysActiveunder a steward hold reaffirmed when asked directly. RFC 0158 staysActive; its recovery rows carry operator preconditions no unattended run satisfies.docs/KNOWN-LIMITS.mdnow lists all four open RFCs, having carried RFC 0043 asActivefor the seven weeks since it was Accepted. TheConformance Soakworkflow has been red onmainsince 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-23the 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
Acceptedin 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
Acceptedon 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.mdand 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
Closedor "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.mjsmatched the bare substringclosedanywhere 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, withtransferredreported 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 reachedAccepted: 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 ofopenwop: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/v1correction, 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### Securitychangelog 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-standardis 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, readingblockedas 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=wherewebhooks.mdmandatessha256={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 asubscriptionIdthe 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 suite1.136.15, with the new pin building the header by hand from the spec. replay.mdgains 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.tswitnesses host-initiated fan-out suppression —blockedin 41 ms on a default host, PASS in 7.6 s with an opt-in, and RED against a host patched to re-deliver. NEWSECURITY/threat-model-replay.mdfiles the harm as integrity of externally-projected facts, not leakage: nothing escapes, and a correct re-emission defeats subscriber dedup by construction. Protocol invariantreplay-fanout-no-refireadded (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-exhaustionasserts whatfailure-pathcannot — not that failing work terminates, but that attempts stop — PASS in 4030 ms and RED against a host patched to retry post-terminal. Suite1.136.11 → 1.138.1, 465 scenarios.
1.8.0
2026-08-18the 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 withcompensationby 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.waiveRequiresApprovaladded (§B) with escalation as a floor a node-levelfalsecannot 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:supportedTriggersstays 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.mdcaveat 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 aforloop 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
replayfork delivered webhooks asserting work it never performed, and a correct re-emission defeats subscriber dedup by construction (fresh envelopeeventId).replay.md§"Host-initiated fan-out": suppress outbound delivery for re-emitted events, read replay-ness from the run not the event type,branchout 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.mdclaimed v1 bundles were "reproducible … against the same host commit"; a host rebuilt one commit and scored 283/22 against 303/2 (its image rannpm install, notnpm 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, andhost.build/host.commitare now documented incertification-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 theMUST NOTon host-generated identifiers sharing the caller keyspace.idempotency_key_mismatchis canonical;idempotency_key_conflictretired. Four artifacts had disagreed because the spec named no error and the leg asserted the409status but never a code. storage-adapters.mdgains §"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 thereforeblockedin 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 deprecatedcapabilitieswrapper;capabilities.mcp.serverUrlsrequired by a scenario and forbidden by the schema;discoverOwnedTenant()reading a field no schema-valid host emits; thetarball_too_largeprobe that uploaded 60 MB into a proxy's 32 MiB ceiling instead of inflating a 64 KB gzip; and eight files whosesoftSkipsat afterctx.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-memorycertified; MyndHyve reached bundle #14 withexecuted-fail 0and 13 profiles certifiable. Values withheld with reasons are recorded as such —replay.sideEffectSuppressionstaysnonebecause "do not perform" and "resolve the recorded outcome" are different obligations, and asuiteVersionpin 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 Soakworkflow'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-16the 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 0146contractProvenanceActive → Acceptedwith 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|blockedper requirement,assertionCountwitnesses), certification bundle v2 with a requiredblockedtotal,--certifyexits 3 on an unclassified return, consumer verifierverifyBundleV2, evidence scrubbing (SR-1 canary), a floor for every catalog profile (discovery-conditional foropenwop-replay-fork, runtime-derived foropenwop-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.mdrunbook). 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.tshad sat inPROFILE_FLOOR_SCENARIOSsince 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.jsonregenerated. - Scenarios say why they return early. 84 silent-return files →
softSkip(kind, reason)/seamAbsent(blocked by default, FAIL underOPENWOP_REQUIRE_BEHAVIOR=true; a 403 is no longer a pass); a zero-assertion file with no reason isblocked, never a pass;behaviorGaterecordsinapplicable/skippedinto the ledger; the advertise-vs-opt-outMUST NOTthrows instead of warning. - Machine contracts reconciled (RFC 0149).
servers[].urlno longer doubles/v1(44 operations resolved/v1/v1/*);protocolVersiongrammarMAJOR.MINORenforced 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-parityhold OpenAPI, AsyncAPI and the SDK path table to one resolution. - Effect identity, replay digest, split-brain (RFC 0150). Layer-2 identity retired the
attemptcomponent (safety-fix; zero deployed keys per the v1 inventory); semantic-request digest v2 (JCS) with golden vectors;crossRegion: fenced-effectsand 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-freecompensation.*events,compensationStatusonRunSnapshot,spec/v1/compensation.md,compensation-replay-no-refire, and the behavioural witness + §21 seams —blockeduntil 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/profileson both families; full 1.0 translation table (froma2a.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.workloadIdentitywith 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-corecanonical /openwop-coredeprecated alias (both-or-neither; bundles report the alias inaliases), generated core-standard manifest, extension registry with a measuredcoverageblock (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.invokewas a node no host shipped → conformance-reservedcore.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 atopenwop-conformance/v1.123.0(the interim minors exist as commits only). SDKs release fromopenwop-sdks:@openwop/openwop1.7.0 /openwop-client1.6.0 (RunSnapshot.compensationStatus), Go unchanged at 1.5.0.
1.6.0
2026-08-11the 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.formatadvisory hint),0138(pack vendor-extension hatch),0142(store-gatedartifact.createdemission),0143(tool-result trust is monotone),0144(which capability families the core schema declares),0145(registrationSourceper-type facet).0136/0143/0144wentDraft → Active → Acceptedwithin the cycle;0145filedActive → 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 bycheck-capability-declaration-classes.mjsagainst a reviewed ledger (25 declared / 11 extension). Gaps G1 (which resolver arm a host lands on), G2 (duplicate§hostsections), and G3 (rule enforcement) all closed.capabilities.schema.json83 → 88 properties.
- RFC 0145 —
registrationSourceper-type facet. A new OPTIONAL discovery facet onartifactTypes.typesdisclosing 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
untrustedabsent a named basis fortrusted, and no transformation (summarize, store-then-recall) launders it — theFRMD-F1-1laundering path, previously unforbidden in general. Newtool-result-trust-monotoneprotocol invariant (SECURITY 159 → 160); both halves host-witnessed.
- RFC 0136 —
WorkflowVariable.format, an advisory presentational hint. One optional string onWorkflowVariable; in RFC 0124 deferred mode a host MUST copy a chain parameter'sformatonto 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-gatedartifact.createdemission witness. Leg B went red first, on a live wire defect: the host emittedartifactTypeIdand neither required field, so everyartifact.createdsince 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 ofstore: 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-bearingx-openwop-app.canvasextension 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 towebhooks.mdv1.1 (1.68.1/1.68.2); the handoff-schema fixture rebuilt to actually fire the gate + gated onagents.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-lessMUST-NOT, which would be the exact RFC 0144 defect written into the SECURITY doc.
- Packaging — the vendored contract is addressable.
schemas/CORPUS-STAMP.json+ newcheck-npm-pack-contents.shpins 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-conformanceadvanced1.66.0 → 1.72.2this 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), and0121(needs legal/ToS clearance for a named provider) stayActive— each blocked on evidence a steward cannot manufacture.
- Counts.
Accepted 141 · Active 3 · Draft 1(145 RFCs, excluding the0000template; Active = 0035, 0111, 0121; Draft = 0038 Parked). SixActive → Acceptedthis cycle.
1.5.0
2026-08-08the 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,
Accepted2026-07-22). A third explicitprincipalkind for tool-enabled public agent surfaces — no default baseline, no secret reach, SSRF-guarded egress, write-egress gated, opaque audit. Landed reference-impl atActive, graduated protocol-tier same-day on the openwop-app witness (5 gated scenarios 10/10 underOPENWOP_REQUIRE_BEHAVIOR=true). Five SECURITY invariants ride with it. - Workflow-chain composition — sub-chains + produced variables (RFC 0133,
Accepted2026-07-23; RFC 0134 + 0135 alongside). Sub-chain co-expansion with bounded depth and child-tenant scoping (two invariants),EdgeConditiongainstruthy/falsyoperators (0134), and chains can be markedinternal— 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"withtemplates[]/fields[]; the field-type vocabulary is byte-identical to chat-cardinputs[].typeso neither can widen alone, and unrecognizedvendor.*/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 → Acceptedgate could pass against a host that never implemented it; the behavioral leg now exists and gates onbehaviorGate, 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 dottedhost.*keys had made schema-following hosts invisible. - Vendor-extension hatch on pack manifests (RFC 0138,
Active2026-08-06).host-extensions.mdrequired unknown vendor-prefixed fields be treated as opaque while every manifest'sadditionalProperties: falsemade 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 thepack-manifest-extension-opaqueinvariant. 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: thereplay.sideEffectSuppressioncapability, the recorded-outcome-or-fail-closed MUST, and thereplay_source_missingtyped failure — Layer-2 idempotency provably cannot cover a fork (newrunId⇒ disjoint key space). Accepted on the openwop-app witness (sourceeffectCount: 1, replay0, cancelled-source fail-closed; eight per-fix sabotages). Honest correction same cycle: the RFC's motivation claimed v1 constrained nothing here —replay.mdcaveat 1 had always MUST-ed it; corrected in place. - Legacy artifact-type identifiers (RFC 0141,
Acceptedon 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 historicalartifact.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,Active2026-08-08). The corpus's onlyartifact.createdemission MUST hangs off thestorefacet, 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;Activeuntil a host advertisesstore: truehonestly — acceptance MUST NOT be manufactured by advertising to graduate it. - Registry + extension policy ratified (RFC 0043,
Accepted2026-07-07). The one-stop governance policy — namespace reservation, submission/yank/key-rotation, IPR posture — live atdocs/governance/registry-policy.mdand cited as authoritative from normative spec. - Conformance suite
1.54.0 → 1.67.0, 400 → 408 scenario files — published continuously on per-packageopenwop-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, andartifactCreated.artifactType(required); behavioral legs cover form-content instantiation, extension opacity, replay suppression, and store-emission.behaviorGatePresentjoins 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), andpack-manifest-extension-opaque(0138). - Honest corrections are part of this release's surface. The month-red
Conformance Soakwas 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 inopenwop-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.0corpus 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-07agent-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 OPTIONALAgentManifest.role("skill" | "assistant") — EXPLICIT, never inferred (handoffpresence does NOT implyskill; an assistant MAY ship a typed entry contract). An absentroleis unconstrained — byte- and behavior-identical to today, no profile binds it. A manifest that opts in withrole: "skill"binds the Skill profile, encoded inagent-manifest.schema.jsonas anif role==="skill"conditional: it MUST declarehandoffand MUST constrainmemoryShapeto scratchpad-only (conversation/longTermMUST befalse/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 §Cdegraded[]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 (nocapabilities.*advert, no host behavior), there is no per-host interop variance — the always-onagent-manifest-role-profile.test.tsis the universal witness.Draft → Acceptedvia the bootstrap single-maintainer comment-window waiver (CONTRIBUTING.md§"Bootstrap-phase notes"), architect-reviewed by the steward (explicitroleopt-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). Addsagent-skill-profile-stateless(RFC 0131 §B) with its matching always-on public conformance test.
1.3.0
2026-07-07the 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[]gainsstream(broker message) +change(warehouse CDC record) withop-carrying envelopes reusing the RFC 0099 SSRF-guard + content-freetrigger.events (0127). An OPTIONALpermittedPurposes: string[]label ridesTriggerEvent+ the A2Ametadata.openwop.namespace; an advertising host MUST re-emit onward (MAY narrow, MUST NOT widen), and[]-labelled data MUST NOT be forwarded (0128). An OPTIONALcapabilities.dataResidency {regions[]}+residency:{region}onPOST /v1/runsmakes region admission control a falsifiable MUST (accept iff advertised, elseresidency_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 OPTIONALworkflowChainPacks.deferredParametersmode that keeps chain{{params.*}}overridable per run without non-portable runtime tokens — the host materializes chainparametersintovariables[]+ rewrites to spec'd bindings, replay-deterministic viaRunSnapshot.variables. RFC 0125 mirrorstriggerRuleontoFragmentEdgefor pack-declared fan-in / error-routing. Both single-witness on the reference host. - Data-parallel per-item dispatch (RFC 0126
Accepted). An OPTIONAL index-alignednextWorkerInputs[]onNextWorkerDecisionfans ONEchildWorkflowIdover 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 realcore.dispatchexecutor. - Self-hosted runner (RFC 0122
Accepted). New OPTIONALselfHostedRunnercapability: 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-iframekind:"frontend-plugin"loader with deny-egress CSP + host-RPC allowlist (0117/0119); portable secret-freecachePrefixIdprompt-prefix caching with tenant-keyed cross-tenant isolation (0116); OPTIONAL RFC 6902 A2UI delta-frame transport over the unchanged recordedui.a2ui-surfaceenvelope (0114). All single-witness, steward-curl-verified. - Connection-pack provider
vendorgrouping (RFC 0123Accepted). OPTIONAL presentationalprovider.vendorstring 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 theaiProviders.authModesenum + the protocol-tiersubscription-credential-user-scope-onlyMUST (credential_scope_forbiddenon 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) — seedocs/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.inputsare preserved verbatim through expansion, and a parameter-distinct persisted-identity rule prevents silent overwrite. RFC 0124 §Security: asensitiveparam MUST materialize assource:"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-timeRunSnapshot/at-rest leak. - RFC 0013 errata (doc/test-only). Expanded node ids are unbounded — hosts MUST NOT cap
idlength (WorkflowNode.idhas nomaxLength); the host-expansion test seam decoupled onto a conformance-onlyworkflowChainPacks.hostExpansionSeamflag + theworkflow-chain-samplefixture bundled, so RFC 0013's Phase-3 host-expansion acceptance item is witnessed 6/6 harness-tier. - SECURITY invariants
145 → 148(protocol-tier115 → 118). Addssubscription-credential-user-scope-only(0121 §B.8),runner-credential-non-transit(0122, BYOK SR-1 parity), andrunner-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 inINTEROP-MATRIX.md+docs/KNOWN-LIMITS.md; none is claimed as independent-organization dual-witness.
1.2.0
2026-06-30the 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 OPTIONALagent.model({provider,model}) onconversation-turn.schema.jsonrecording which model produced arole:'agent'turn, gated on the new optionalconversationTurnModelProvenance.supportedcapability. NON-SECRET / NON-PII byadditionalProperties: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 → Acceptedvia 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 ephemeralchannel.presenceRunEvent (channel-presence-payload.schema.json) fortype:'channel'conversations — present-member subject refs + optional per-member typing — gated onchannelPresence.supported. Hosts MUST NOT persist it to the replayable log; it is replay/:fork-invisible (the load-bearing distinction from the persistedconversation.exchangedturn). Membership-gated (DEFAULT-DENY, CTI-1), NON-PII (opaque subject refs only). Server-free shape scenario;Draft → Acceptedvia 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 rev00332-gm2+ MyndHyve tier-2 revs00511-len/00512-pej/00510-jiv), every gated scenario re-driven non-vacuously by the steward. 0112 compact tool projection — OPTIONAL?view=compactonGET /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 — OPTIONALtokenBudget/rank/queryonMemoryListOptionstoken-bounding the live injection read (rank:'relevance'delegates tomemory.search, no new ranking primitive) +memory.injectionBudget; SR-1/CTI-1 hold by construction. 0115 run transport economy — OPTIONAL strongETag+If-None-Match/304+Content-Encodingnegotiation onGET /v1/runs/{runId}+restTransport. PerGOVERNANCE.mdtier-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 — OPTIONALmultiAgent.executionModel.contextBudget(transcript token budget + a summarization contract whose substitutions record a content-freecontext.summarizedevent with a replay-reusedsummaryRef). 0114 A2UI surface deltas — OPTIONAL host-side RFC 6902 delta-frame transport (a2ui-surface-delta-frame.schema.json,?a2uiDelta=1) over the UNCHANGED recordedui.a2ui-surfaceenvelope; every A2UI invariant MUST hold post-patch. 0116 portable prompt-prefix cache — OPTIONAL secret-freecachePrefixIdonctx.aiEnvelope.generate+ provider-scopedaiProviders.promptPrefixCache+ cost-onlycacheReadTokens/cacheWriteTokensusage fields, with tenant-keyed cross-tenant isolation (prompt-prefix-cache-cross-tenant-isolation) + replay invariance. - RFC 0117 — Front-end plugin packs (
Active) + normative surface. Additivekind:"frontend-plugin"registry pack + OPTIONALhost.uiPluginscapability 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 closedpostMessagehost-RPC allowlist (ui-plugin/1, with an RFC 0059-style optimistic-concurrencyversiontoken), 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 fourfrontend-plugin-*invariants, and the gatedfrontend-plugin-packs.test.ts.Active → Acceptedwaits on the openwop-app reference host (ADR 0153 canvas editor) advertisinghost.uiPlugins. - RFC 0119 — Mechanism-neutral plugin isolation (
Active; amends RFC 0117). Reconciles RFC 0117's property-level isolation prose with itsconst "cross-origin-iframe"schema pin: widenscapabilities.uiPlugins.isolationto a categorical model (enum [cross-origin-iframe, wasm, process, container, vm]+^x-host-*, reusing the RFC 0035 vocabulary,cross-origin-iframethe default) and reframes thefrontend-plugin-isolationMUST-NOT as mechanism-independent. Strengthens §Host-RPC for non-browser transports (transport-guaranteed framing + per-channel ordering; forwasm, no host import beyond the singleui-plugin/1entrypoint). 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: additivefanOutPolicy:'parallel'+ an optionaljoinPolicy(modewait-all/quorum/first/race ×onChildFailurecollect/fail-fast/absorb) +maxConcurrencyonDispatchConfig, emittingcore.dispatch.fanOut/core.dispatch.joinrun-events with a replay-deterministicmergeOrder; gated ondispatch.fanOutSupported, default stays'sequential'. Graduated on dual-witness discovery + behavioral evidence (openwop-app prod + MyndHyve tier-2 rev00513-qib, steward-verified via thePOST /v1/host/sample/dispatch/fanoutseam:joinOutcome:"satisfied", 3 children,mergeOrderof 3; corroborated by MyndHyve'sdispatch-fanout-parallel.test.ts10/10). Honest tier-2 + reference-host, with the per-hostjoinModes/onChildFailureModessubset divergence the gating exists to surface. - RFC 0120 — Connection-pack
apiHostscredential-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 OPTIONALprovider.apiHostsarray (strict bare registrable hostnames; IP/wildcard/port/single-label rejected) — REQUIRED at the manifest whenreach:openapiand 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 gatedconnection-pack-apihosts.test.ts. Graduated single-witness tier-2 + reference-host: openwop-app ran the published1.46.0leg non-vacuously underOPENWOP_REQUIRE_BEHAVIOR=true(PERMITsgraph.facebook.com, fails closed onevil.com/notfacebook.com/facebook.com.evil.com); MyndHyve honestly opted out of the brokered-egress arm. - SECURITY invariants
138 → 145(protocol-tier108 → 115). Adds the fourfrontend-plugin-invariants (RFC 0117),prompt-prefix-cache-cross-tenant-isolation(RFC 0116), and the twoconnection-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 ownopenwop-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 stayActivepending a host advertisinghost.uiPlugins; RFCs 0118 / 0120 graduated on tier-2 + reference-host evidence, recorded as such — not independent-org dual-witness. Each is documented indocs/KNOWN-LIMITS.md+INTEROP-MATRIX.md.
1.1.9
2026-06-24the 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 kindui.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 suite1.26.0(openwop-app rev00204-v75+ MyndHyve, byte-identical core schemasha256 68f977c1…). Newschemas/envelopes/ui.a2ui-surface.schema.json+ five SECURITY invariants + athreat-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 Stablei18n.mdAccept-Language/Content-Languagenegotiation verbatim and adds only the data model + a per-section field merge (resolveSection: exact → language-family → base). New core specspec/v1/localized-content.md, four schemas, acontentcapability block, OpenAPI delivery + admin paths, three protocol-tier SECURITY invariants. Dual non-steward host evidence (openwop-app + MyndHyve) vs suite1.27.0. - RFC 0104 — Portable HITL approver routing (
Accepted, 2026-06-19). Three optional advisory fields on thekind:"approval"interrupt payload (approverGroupRefs,approverRoleRefs,audience) + a top-levelinterrupt.approverRoutingcapability, so group / role approver routing is portable + capability-gated. Theaudiencehint defaults to the resolved eligibility union and is fixed history on replay /:fork. Dual non-steward host evidence with honestly-divergentaudiencesupport (openwop-app + MyndHyve) vs suite1.28.0. - RFC 0105 — Speech synthesis adapter (
Accepted, 2026-06-21). AdditiveaiProviders.speechSynthesis: "supported"exposingctx.callSpeechSynthesizer(...)→ a binary audio asset (host-servedurlor inlinebase64+mimeType), a verbatim parallel ofctx.callImageGenerator. Dual independent non-steward host evidence vs suite1.29.0on the identical locked envelope with honest provider / asset-arm divergence (openwop-app MiniMax /urlrev00265-wtx+ MyndHyve Gemini /base64rev00280-dcs). Reuses existing SSRF + untrusted-media guards (no new invariant). - RFC 0106 — Real-time voice session profile (
Accepted, 2026-06-24). AdditiveaiProviders.realtimeVoiceprofile: streaming transcription (ctx.callTranscriber), a streaming arm onctx.callSpeechSynthesizer, and avoice.*turn-taking / barge-in run-event taxonomy (run-event types100 → 107) with a distinct session-boundstreamReflive-conduit handle. Per-capability graduation: the transcription core is dual-witnessed (openwop-app rev00294-nhz+ MyndHyve Gemini-multimodal rev00503-row) vs suite1.34.0; the optionalsynthesis/turnDetection/bargeInarms stay single-witnessed + conformance-gated.voice-transcript-untrusted+voice-bargein-no-partial-leakare protocol-tier;voice-interim-not-durable+voice-streamref-tenant-boundstay 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-facingctxresolves aPromise+ emits to the durable log, never a liveAsyncIterable). - RFC 0100 — Async / durable A2A tasks (
Accepted, 2026-06-24). Graduates thea2aasync / durable composition surface (durableTasks+pushNotifications) on dual-witness evidence vs suite1.34.0(openwop-app rev00299-hns+ MyndHyve rev00505-seh): a durabletasks/getover 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 — suite1.34.0ships no resubscribe gated subtest, so advertising it would be vacuous.a2a-push-egress-ssrfwas already protocol-tier (no graduation). - RFC 0108 — Self-hosted / OpenAI-compatible provider class (
Accepted, 2026-06-24). Additive optionalaiProviders.selfHosted[](a subset ofsupported[]) 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-tierself-hosted-endpoint-no-disclosureinvariant. 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_ENABLEDunset ⇒selfHosted:[]). Passes bothaiproviders-selfhosted-shape(always-on) andaiproviders-selfhosted-honesty(gated) non-vacuously vs suite1.37.0. - RFC 0101 — Multi-party group conversation (
Accepted, 2026-06-22). Un-parked + graduated via the/prdfive-architect pass. Three additive capability-gated facts so N agents co-participating in one shared transcript are cross-host-observable: an optionalparticipants: AgentRef[]roster onconversation.opened, a conditionally-requiredspeakerIdon agentConversationTurns, and amultiPartyConversationcapability 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). Reusesroster-attribution-no-content(no new invariant). - RFC 0107 — Publishable declarative pack kinds (
Active) + chat-card-kind correction (safety-fix). Lands theActiveRFC 0107 (opened + flippedDraft → Active2026-06-23): the publishedregistry-version-manifest.schema.jsongains akinddiscriminator (nodedefault) + per-kind declarative payloads (artifactTypes/provider/chains/prompts/cards), withruntimere-tightened by anif/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-packkindiscard(RFC 0071 authoritative), notchat-card; zero such packs were ever published (safety-fix). - SECURITY invariants
125 → 138(protocol-tier99 → 108). Adds the five A2UI invariants (RFC 0102), three localized-content invariants (RFC 0103), the voicevoice-transcript-untrusted+ graduatedvoice-bargein-no-partial-leak(RFC 0106), andself-hosted-endpoint-no-disclosure(RFC 0108), plus newthreat-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 ownopenwop-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 SDKctxtype additions (ctx.callTranscriber, thecallSpeechSynthesizerstreaming arm, the interrupt + A2A types) are sequenced into theopenwop/openwop-sdkssibling 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'stasks/resubscribestays 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 indocs/KNOWN-LIMITS.md+INTEROP-MATRIX.md.
1.1.8
2026-06-14the 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, Pythonopenwop-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 togithub.com/openwop/openwop-sdks/go(re-pin required); npm + PyPI names unchanged. The pack-manifest schemas stay normative here. A coordinatedv*spec release must be matched by SDK tags inopenwop-sdks. - RFC 0099 — external-event trigger ingestion
Accepted. Floor + graduation: the content-freeTriggerEventenvelope (ctx.triggerData), theTriggerSubscriptionRegistrationcreate contract served byPOST /v1/trigger-subscriptions, the additivetriggerBridge.ingestioncapability sub-block, and SECURITY invariantstrigger-ingestion-ssrf+trigger-ingestion-content-redaction. Graduated on dual live evidence vs suite1.25.0(reference openwop-app00177-75t+ non-steward MyndHyve00271-cj5, both steward-curl-verified,trigger-ingestion.test.tsnon-vacuous). - RFC 0100 — async / durable A2A tasks (
Active; floor landed). Newa2acapability block ({supported, agentCardUrl, streaming?, pushNotifications?, durableTasks?}) + the persistedA2ATaskStateprojection (taskId==runId, lowercase-hyphenstate) +a2a-integration.md§"Async / durable Tasks" + SECURITY invarianta2a-push-egress-ssrf. Reference leg fully proven (openwop-appa2a-task-roundtrip10/10 non-vacuous); staysActivepending a non-stewarda2awitness. - RFCs 0096 / 0097 / 0098 — reviewable-learning proposals · standing goals · agent-platform portability
Accepted. Floor + dual-live-evidence graduation:agents.proposals(inertproposal.schema.json),agents.goals(judge-basedgoal.schema.json), the top-levelportabilityblock (export-bundle.schema.json, refs-only,dryRun:truewhenimport:true), 5 protocol-tier SECURITY invariants, 5 content-free events. openwop-app reference (00174-6m6,portability.import:true) + MyndHyve non-steward (00269-ljm, honestimport:falseopt-out). - RFC 0095 — connection packs
Accepted. Newkind:"connection"pack (portable signed provider definition the RFC 0045/0047providerstring resolves against) +connection-pack-manifest.schema.json+ standalonespec/v1/connection-packs.md+ theconnection-pack-no-credential-materialprotocol-tier invariant. Dual evidence (MyndHyve00268-x9l+ openwop-app00160-kjq). - RFCs 0093 / 0094 — protocol hardening + wire-shape reconciliation
Accepted. 0093 pins four security/correctness gaps (webhook delivery-time egress re-validation + the protocol-tierwebhook-cross-tenant-isolationinvariant; signed interrupt-token lifecycle; retryable responses MUST NOT replay from the dedup cache; approval-gate timeout auto-rejects). 0094 repairs published-artifact defects (the unsatisfiablecreateRunschema →unevaluatedProperties:false+ a satisfiability probe;cancellingstatus; vendor-eventanyOfclosure; single-sourcedai.message.chunk; completedInterruptPayload.kindunion;capabilities.grpc+limits.maxRequestBodyBytes). - RFCs 0090 / 0091 / 0092
Accepted. 0090 — agent verifier turn (agent.verifiedevent,verifiercapability,multiAgent.executionModel.version: 6,verifier-no-content-leak); 0091 — multimodal perception input onctx.callAI(content: string | ContentPart[], gated onaiProviders.input.modalities[]); 0092 — agent-levelrequiresCapabilities[]projecting to the RFC 0072degraded[]field. - RFC 0089 — conformance certification bundle
Accepted. Theopenwop-conformance --certifygenerator captures discovery (canonical-JSON SHA-256), derivesclaimedProfiles, records each scenario's terminal state, and never lists a vacuously-run scenario inresults.passed; a real reference-host bundle is committed + round-trip-verified. - Conformance suite —
@openwop/openwop-conformance1.18.1 → 1.25.0 across the cycle (independently versioned, published on its ownopenwop-conformance/v*tags). Carries the RFC 0089–0100 scenario additions (trigger-ingestion, the asynca2a-task-roundtripsubtests,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, experimentalx-host-openwop-workforce), the white-label foundation batch (deploy-posture gating, brand stamping,.envleak guards, an explicit-?tenantId=*fix that closed a latent cross-tenantGET /v1/agentsvisibility leak), and a board-rename +guidedautonomy level. Operator surface only — no wire change. - Docs + editorial. Full post-split + post-graduation drift sweeps: status-label normalization onto the
Stable / Stabilizing / Draft / Experimentallegend, SECURITY/RFC count reconciliation, per-host conformance-evidence suite-version syncs, theINTEROP-MATRIX.mdrewrite into a lean public matrix + theGOVERNANCE.md"Acceptance evidence tiers" taxonomy, and post-split path requalification across README/ROADMAP/PUBLISHING.interrupt.mddocumentssuspendas the accepted alias ofinterrupt(additive, host SHOULD expose both).
1.1.7
2026-06-02the 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-platformmeta-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[]+ hostctx.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 reachedAccepted. - Real-isolation WASM sandbox reference host (RFC 0035). New
examples/hosts/wasm-sandbox/executes pack-loaded typeIds as WebAssembly; all 7 testablenode-pack-sandbox-*SECURITY invariants graduatedreference-impl → protocol. RFC 0035 itself staysActive(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.mdrows 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;
AgentOrgChartDepartment/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 addedtools.list/tools.get+getArtifactacross all three. Newsdk/parity-expectations.jsondeclares a per-SDK status (typed/excluded) for every OpenAPI operation, andscripts/check-sdk-parity.mjs(openwop:checkstep 7) fails on any undeclared route ortyped-surface regression.sdk/PARITY.md's stale "34/34/34 as of 2026-05-15" headline corrected to 44 typed / 4 excluded (the fourpacks-testwrite-mirror operations — a server-side conformance affordance, not a client surface). This release publishes that surface:openwop-client1.1.7 (PyPI) +github.com/openwop/openwop/sdk/gov1.1.7 finally carry the 17 agent-platform methods, and@openwop/openwop1.2.0 (npm — a minor, not a patch, becauseclient.prompts.getnow returnsPromptTemplate | nullto match the other get-by-id helpers + the Python/Go SDKs: a404resolves to the null sentinel while a400 prompt_ref_ambiguousstill 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-methodsymbolscheck 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
429envelope conformance (CF-6): the sample host now emitsdetails.scopefrom the canonical closed enum ("tenant" | "route" | "global" | "key") perrest-endpoints.md §429instead of internal limiter names (which moved to a non-normativedetails.reason), and honorsOPENWOP_FORCE_RATE_LIMIT=trueso 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$idbumps. - Release infrastructure + doc hygiene. Publish jobs are now idempotent — a corpus
v*tag skips already-published package versions rather than partial-failing (PUBLISHING.mddocuments the property).generate-protocol-status.mjs --writeself-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 + renamed1.1.7(1.1.6 was consumed by the TS SDK patch); the committedpublic/site was rebuilt + deployed to openwop.dev to surface the graduations; and theINTEROP-MATRIX.mdpass-rate header was corrected to mark1.15.0as the _last_ re-measurement (the published suite has since advanced to1.18.1; a full re-measurement is pending). - OpenAPI agent-path disambiguation (audit response).
/v1/agents/{agentId}and/v1/agents/{agentId}/deploymentsnow constrainagentIdwithpattern: '^(?!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/deploymentswas structurally two-way). redocly's purely-structuralno-ambiguous-pathsrule (which can't see the pattern) is scoped off for exactly those two paths inapi/.redocly.lint-ignore.yamlwith a documented rationale — the rule stays enabled globally. Non-breaking:roster/org-chartwere 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/openwop1.1.6,@openwop/openwop-conformance1.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/cli0.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-28OpenWOP 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 owncli/v*SemVer line; published through the existing OIDC publish pipeline with provenance (a newpublish-clijob mirrorspublish-ts-client). The 4085-line TypeScript bundle runs understrict + noImplicitAny: true; 149node --testcases gate every release. A new/cliroute in the demo app surfaces install + command catalog; a new homepage card onopenwop.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 (withOPENWOP_MESSAGING_AGENT_DISPATCH_TIMEOUT_MSabort 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 onlistMessagingTurns, and a?limitclamp 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) graduatedActive → Acceptedon MyndHyve production revworkflow-runtime-00394-jun(inventory + dispatch seam wired withtoolSurfaceallowlist filter + attributedagent.reasoned/agent.decided). 0072 (normativeGET /v1/agents) shipped. 0074 (tenant-scoped manifest-agent inventory — additiveinstallScope: 'host' | 'tenant') graduated on multi-tenant revworkflow-runtime-00398-vupwith steward-curl-verifiedA→1 agent, B→empty + cross-tenant 404. The 31 agent manifests on the workflow-engine host become runnable viaagents.manifestRuntime+ AgentRegistry + loader + dispatch. - RFC 0071 (artifact-type packs + chat card packs)
Acceptedoverall. Phase 1 graduated on MyndHyve Phase-1 adoption (16 → 7 reconciled types served asvalidated: true, validation: "open", registrationSource: "host"). Phase 2 graduated on Slice-B revworkflow-runtime-00402-bey— the realcore.chat.cardExecute→ctx.aiEnvelope.generatebinding withhost.aiEnvelopeadvertised +contentTrust: "untrusted"on card-input-derived prompt segments.artifact-type-packs.md+chat-card-packs.mdpromoted 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 rootedagents/secrets/ etc. at the doc root (nocapabilitieswrapper), 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 viaspread. Phase 4 (drop the wrapper-fallback + nested mirrors) defers to a future minor. - RFC promotions Draft → Active. 0054 (run-diff —
GET /v1/runs/{runId}:diffendpoint + 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: falseMUST → 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-freeagent.memory.consolidated/commitment.firedevents). 0069 (host-extension safety contract forexec-class tools — normative MUST-NOT carving exec out of protocol-tier + theexec-must-not-be-protocol-tierinvariant). All additive. - Registry hardening. Three published
core.openwop.agents.*packs republished as@1.0.1with theirsystemPromptRefbodies now bundled — the bundler droppedprompts/from the tarball at @1.0.0 and an RFC 0003 §C fail-loud host (MyndHyve) correctly rejected them. Newscripts/check-pack-prompt-refs.mjs(wired intoopenwop:checkstep 7) fails CI at build time if any source pack'ssystemPromptRefdoesn't resolve to a bundlableprompts/file. Separately: 81 pack-versionindex.jsonfiles corrected to declare the real publisher signing keys (openwop-team-1,myndhyve-internal-1,community-demo) instead of the staleopenwop-registry-rootdefault;check-registry-signer-consistencyguard added. - Reference workflow-engine:
/readinessreports managed-provider health. Whenproviders.jsonadvertises amanaged: truetier (e.g.,openwop-free) but its server-held key was never seeded,/readinessreturns503 { status: "degraded", checks: { managedProviders: [...] } }with a per-providerdetailnaming 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 toAcceptedawaits 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. Thev1.1.4tag from 2026-05-26 silently did not trigger the publish workflow (no run record); thisv1.1.5corpus-aligned tag catches the TS SDK / Python / Go / conformance up.DEPLOY.md§6 corrected — the from-scratch--env-vars-file+--set-secretsform was a footgun for live redeploys; safe-redeploy recipe documented separately. - Site updates. Homepage
§01.5 "Try it from your terminal"card onopenwop.devintroduces@openwop/cliwith a dark-bg install snippet + capabilities list + deep-link to the in-app/clicommand catalog.PUBLISHING.mdartifact table gains the CLI row noting independent SemVer +cli/v*tag pattern.
1.1.4
2026-05-26MyndHyve 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 0057memory.writtentyped event helper + the round-2 SDK-migration finishers (runsClient/interruptsClient/promptsClient/streamsClienton 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-conformance1.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 stalesecrets.scopesallowlist inredaction.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 revision00211-69wagainst@openwop/openwop-conformance@1.6.0— 28 PASS / 0 FAIL (commit85275cdfon the MyndHyve side). RFC 0050 (SAML/SCIM) + 0054 (run-diff) stayDraftper documented MyndHyve opt-outs. - MyndHyve round-3 graduations (3 RFCs Active → Accepted 2026-05-26). Revision
workflow-runtime-00217-q7cadvertisescapabilities.prompts.agentBindings: true(RFC 0029),aiProviders.maxInlineMediaBytes: 10485760+modelCapabilities.advertised: ['vision-input', 'image-output'](RFC 0055), andcapabilities.memory.attribution.{supported: true, emitsWriteEvents: true}with canonicalmemory.writtenevent dual-emitted alongside vendorx-host-myndhyve-memory-written(RFC 0057). Curl-verified onhttps://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: 4advertisement +replayDeterminism.{supported: true, llmCacheKeyRecipe: "spec-rfc-0041", refusalDivergenceEmission: true}block. Theversion: 1-4ladder 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) filedDraftwith 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 documentedPOST /v1/host/sample/*seam + all scenarios green); 0058 + 0061 graduated toActivepending second-host M2 enforcement. - RFC 0028 Tier-2 post-promotion strengthening. Workspace-membership normative + canonical
workspace_membership_required403 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
runTimeoutMsenforcement. SQLite: ports RFC 0022 dispatch/subWorkflow + 0026 + 0031 + 0056 + 0057 + 0058 from Postgres. Reference workflow-engine advertisescapabilities.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 consumingconversation.{opened,exchanged,closed}events), #16 (A2A peer placeholder, forward-compat), #20 (Publish-to-registry helper banner), #25 (sticky-note canvas annotations via newclientOnly: trueNodeCatalogEntryflag). The app-buildable plan closes at 23 ✅ / 2 🟡 / 0 ❌; the two 🟡 items are structurally unblockable from the openwop side and tracked indocs/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-stewardx-openwop-formpack (PR #232). Pilot annotation onbrand.persona.discover.config.jsonaddsprovider-picker+model-picker { dependsOn: ["provider"] }(RFC 0066 normative cascade-clear). Path-to-Accepted for RFC 0066 unlocks once MyndHyve re-pins the newmanifestHash. Bonus:scripts/emit-pin-json.cjsemits 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 inconformance/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-formvendor extension on packconfigSchema). - Honest non-graduations + opt-outs. RFC 0058 wall-clock arm took two rounds — MyndHyve initially advertised
maxNodeExecutionsin error, honestly retracted viadocs/openwop-adoption/rfc-0058-round-3-retraction.md, then landed the real arm on 2026-05-26 (limits.maxRunDurationMs: 600000now advertised;maxLoopIterations: nullhonestly absent — host attests run-create clamping + canonicalcap.breached { kind: 'run-duration' }emission). RFC 0058 staysActivepending second-host adoption. RFC 0035 (sandbox), 0036 (multi-region), 0050 (SAML), 0054 (run-diff) opt-outs documented atdocs/openwop-adoption/round-3-closure-2026-05-26.mdwith re-evaluation criteria. RFC 0058 round-3 closure agreement loop documented atdocs/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: Nidentifier (per the 2026-05-24 external-reader feedback). Siteopenwop.dev/spec/v1/*regenerated to pick up RFC 0045–0057 + cohort promotions + 404 page (PR #164). Reference-app SDK migration finished —runsClient/interruptsClient/promptsClient/streamsClientall on the published SDK (cookie-mode SSE stays on native EventSource pending an SDKcredentials: 'include'hook).
1.1.3
2026-05-23coordinated 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 coordinatedparseRefusal()+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.tsschema lookup +promptStore.ts/promptCompose.tsfixtures lookup) — both cases of__dirname + '..' × Novershooting 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 singleENOENTcrash, not 129 real conformance gaps. - RFC 0041 §B Phase 4 closes — replay-divergence-at-refusal executor wiring lands the last
it.todofrom the 5-track audit harness. The workflow-engine's:fork mode: replaypath now emitsreplay.divergedAtRefusaland fails witherror.code: 'replay_diverged_at_refusal'when an envelope kind diverges between source and replay (both directions). Gated onOPENWOP_MULTI_AGENT_EXECUTION_MODEL_PHASE_4=true. RFC 0041 path-to-Accepted opens (gate: second host advertisingmultiAgent.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 withobservability: '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 commitc4342b5bagainst 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 + derivedopenwop-experimentalprofile + 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) perhost-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 —
registerHostSampleRouteswire-up bug. MyndHyve's/v1/host/sample/*routes were deployed for days but never wired into the runtime (404 in production until commit60b569de). 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). NEWdocs/CONFORMANCE-RUNS-2026-05.md(re-measurement of all 4 reference hosts against@openwop/openwop-conformance@1.4.0+ per-failure taxonomy). NEWdocs/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-responderreplaces the deterministicmock-ainode + managedopenwop-freecredential tile by default); Copy/Export buttons on the event-stream view; Cloud Run deploy-plumbing close-out (vendoredschemas/+ dual-mountconformance-fixtures/so the bundled host resolves sibling-repo paths under/app/lib);.gitignorefor 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
TechArticlestructured data on every spec doc. Star-on-GitHub CTA in the marketing footer.
1.1.2
2026-05-21gap-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-conformance1.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. Companionapp.openwop.devworkflow-engine sample app deployed in parallel. spec/v1/ai-envelope.mdDRAFT → 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
reasoningfield + 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 indispatchStructured(); 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 +
slotIndexcorrectness. 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/resolveseam. RFC 0027 §F shareddivergencePointschema 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.subWorkflowruntime variable mapping — Postgres reference impl + dispatch trio); 0023 (conformance agent-event emitters); 0024 (streamingagent.reasoneddeltas + SDK typed-helper rollout); 0026 (provider.usageevent — 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 topacks.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 aspure),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 onctx.trustBoundary='untrusted'runs),core.openwop.agents@1.0.1(raw-JS tool handler — closesOPENWOP-AUDIT-2026-003). Oldcore.openwop.ai@1.1.1+core.openwop.mcp@1.1.0marked 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 theapp.openwop.devsurface.- Workflow-engine sample app — 30+ feature commits covering: BYOK canary echo node + provisioning,
core.channelWrite+ append-with-TTL reducer,capability_not_providedrefusal contract, idempotency body-hash mismatch, quorum-aware approval gate,recursionLimit+conversationPrimitiverefusal,core.subWorkflowexecutor + variable mutation seam, JSON content negotiation,getWorkflowendpoint + strictstreamModevalidation, 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.cascanonical shape, events/polllastSequence+ SSEbufferMsaggregation, 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/postgresqlfor 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.toolReturnedcausationId pairing tightened; envelope-trackit.todoplaceholders drained (sub-tracks E + E2 + A.reasoning-redaction);OPENWOP_REQUIRE_BEHAVIORwired 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-15post-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 thepack-lockfileschema. 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 topacks.openwop.dev) remains gated onSECURITY/external-audit-engagement.md§2.1. community.openwop-team.demore-signed (0bf08cc) — Option-B reconciliation of a 3-way signing-identity drift. The demo pack now ships signed bycommunity-openwop-team-demo-1(over canonicalpack.json) instead ofopenwop-registry-root(over tarball), matching PACKS-MVP-PLAN.md §211's per-tier-key intent and illustrating the per-publisher-identity pattern. Newregistry/keys/community-openwop-team-demo-1.pub+signingKeys[]entry inregistry/.well-known/openwop-registry.json(namespace-scoped tocommunity.openwop-team.demoonly — cannot sign forcore.orvendor.). 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 withoutenableJsonResponse(probe's existingreadSseUntilIdcorrelates frames by JSON-RPC id). Stdio transport — HTTP-incompatible by design — exercised via the newexamples/mcp-stdio-bridge/shim that wraps any newline-delimited-JSON-RPC stdio server as HTTP for the probe (bundledecho-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.mdTrack 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 eachwasm-pack-*.test.tsto its RFC 0008 anchor. The previously-staleOpen spec gapsrowNP1 — WASM ABI for language: wasm packsflipped 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.mdcross-referencesRFCS/at five locations;CHANGELOG.mdrecords the RFC process landing;rfcPR label created in the public repo (#5319e7purple, 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 atActiveeven though all three were promoted toAccepted2026-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-sessionDraft. 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 allAccepted(12 total). 0000 is the template scaffold. Future RFCs revert to the canonical 7-day comment window onceMAINTAINERS.mdlists a non-steward maintainer. - RFC 0012 (Memory Compaction Profile) Phase 3 prep landed 2026-05-14 (promoted to
Accepted2026-05-15 under the bootstrap waiver above):
- Reference host — examples/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 scenarios — memory-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 smoke — examples/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.compactionadvertisement +memory.compactedcanonical event + SR-1 carry-forward invariant for any host that distills short-livedMemoryEntryrows into longer-lived ones. Additive perCOMPATIBILITY.md§2.1. - Tarball-fetch + signature-verify roundtrip vs
packs.openwop.dev(2026-05-13) —conformance/src/scenarios/registry-public.test.tsgains a 4thdescribeblock that fetchescore.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 pernode-packs.md§"Signing recipe" (method=ed25519signs the whole tarball). Closescoverage.mdrow 34's "Remaining: tarball-fetch + signature-verify roundtrip" gap. 6/6 tests pass against livepacks.openwop.dev. - Strict-mode opt-out signaling (2026-05-13) — new
OPENWOP_OPTED_OUT_PROFILES=name1,name2env var consumed byconformance/src/lib/behavior-gate.tsdistinguishes "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-12OpenWOP 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. ZeroDRAFT/STUB/OUTLINEtags 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.mdretention/expiry annex,node-packs.mdlockfile + Component-Model annexes. - 22 first-class JSON Schemas under
schemas/, all JSON Schema 2020-12 with$idathttps://openwop.dev/spec/v1/<name>.schema.jsonandadditionalProperties: falseon 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 hasoperationId+tags+ ≥ 1 error response; every schema referenced via cross-file$ref. Lints clean underredocly lint. New operations:verifyAuditLog,bulkCancelRuns. - AsyncAPI 3.1 (
api/asyncapi.yaml) — every channel binds to a message + payload schema reference. Lints clean underasyncapi validate. - gRPC transport profile (
api/grpc/openwop.proto+spec/v1/grpc-transport.md) — canonicalopenwop.v1.Engineservice; profile-gated viacapabilities.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 +
MemoryAdaptercontract (Accepted). - RFC 0005 — Conversation as run primitive (
Accepted). - RFC 0006 — Orchestrator-supervisor role (
Accepted). - RFC 0007 —
core.dispatchcore node (Accepted). - RFC 0008 — WASM ABI (
Accepted2026-05-13) + Component-Model variant annex. - RFC 0009 — Production-profile conformance (
Accepted2026-05-12). - RFC 0010 — Auth-profile conformance + v1.0 closure umbrella (
Accepted2026-05-12). - RFC 0011 — Auth-scoped discovery (
Accepted2026-05-12).
Multi-Agent Shift (RFCs 0002–0007 + RFC 0008)
- Phase 1 —
AgentRefwire shape;agent.reasoned/agent.toolCalled/agent.toolReturned/agent.handoff/agent.decidedevents;confidenceescalation contract (CP-1);messagereducer. - Phase 2 — Agent capability discovery on
/.well-known/openwop;pack.jsonagents[]extension; agent-pack manifests. - Phase 3 — Agent memory layer:
memoryRefresolution + redaction (SR-1) + cross-tenant isolation (CTI-1) + hostMemoryAdaptercontract. - Phase 4 — Conversation as run primitive:
conversation.start/conversation.exchange/conversation.closesuspend variants. - Phase 5 — Orchestrator-supervisor:
core.orchestrator.supervisortypeId +OrchestratorDecisionschema +runOrchestrator.decidedevent. - Phase 6 —
core.dispatchcore 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 atexamples/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.kindenum gainedwasm-memory,wasm-fuel,wasm-execution-time(RFC 0008 §K). New optional capabilitycapabilities.nodePackRuntimes.wasm.loadedPacks[]surfaces accepted pack names; rejected packs (declared ABI not inabiVersions[]) 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()(parallelnode:http2server, shared spans/metrics store). New optional capabilitycapabilities.observability.otel.exportProtocols[]advertises the supported OTLP transports (http/json,http/protobuf,grpc);spec/v1/observability.mdgains a §"Export protocols" normative section. New conformance scenariootel-emission-grpc.test.tsgates on the array. Opt-in viaOPENWOP_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"perthreat-model-prompt-injection.md§UNTRUSTED.capabilities.httpClient.{supported, methods, ssrfGuard, maxResponseBodyBytes}— universalcore.http.requesttypeId with SSRF guard.capabilities.memory.{supported, maxEntrySizeBytes, ttlSupported}—MemoryAdapterread-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 withopenwop.{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 onOpenwopClientfor every OpenAPI endpoint;HTTP_ERROR_CODEScatalog with 40+ canonical codes;RunEventDoctype +isTerminalRunStatushelper; 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_CODESfrozenset; matching wire types.github.com/openwop/openwop/sdk/go(Go modules) — same surface;HTTPErrorCodesslice; doc comments on every exported symbol;go vetclean.- 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; claimsopenwop-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 claimingopenwop-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 underconformance/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) andnpm run test:strict(--no-file-parallelismfor production-backpressure + OTel envelope coverage). - Behavior-gated:
OPENWOP_REQUIRE_BEHAVIOR=trueflips 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 perPUBLISHING.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-11OpenWOP 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.0on origin. - Tag:
v1.0.0on origin at commit6a637f1.
Scope at 1.0.0
- Spec freeze content per
[1.0] — 2026-05-08entry 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/OUTLINEtags retired;host-capabilities.mdpromoted;i18n.md+compliance.mdannexes 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.devlive with TLS; 3+ packs published with Ed25519 chains. - CI —
npm run openwop:check8-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-08OpenWOP 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 gating —
scripts/openwop-check.sh+.github/workflows/openwop-spec.yml. - Governance —
CONTRIBUTING.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,messagereducer. - Phase 2 (RFC 0003) — Agent capability discovery on
/.well-known/openwop+pack.jsonagents[]extension. - Phase 3 (RFC 0004) — Agent memory layer —
memoryRefresolution, redaction guarantees, hostMemoryAdaptercontract. - Phase 4 (RFC 0005) — Conversation as run primitive —
conversation.start/conversation.exchange/conversation.close. - Phase 5 (RFC 0006) — Orchestrator-supervisor role —
core.orchestrator.supervisornode type. - Phase 6 (RFC 0007) —
core.dispatchcore 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