@openwop/openwop-conformance 1.153.0 → 1.155.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +11 -2
  3. package/api/openapi.yaml +17 -1
  4. package/coverage.md +3 -0
  5. package/dist/cli.js +7 -2
  6. package/package.json +31 -2
  7. package/requirements.json +256 -20
  8. package/schemas/CORPUS-STAMP.json +100 -3
  9. package/schemas/README.md +1 -0
  10. package/schemas/capabilities.schema.json +7 -0
  11. package/schemas/run-event-payloads.schema.json +9 -0
  12. package/schemas/run-snapshot.schema.json +4 -0
  13. package/schemas/subject.schema.json +56 -0
  14. package/src/cli.ts +7 -5
  15. package/src/global-setup.ts +13 -0
  16. package/src/lib/corpus-stamp.ts +125 -0
  17. package/src/scenarios/anonymous-actor-shape.test.ts +6 -1
  18. package/src/scenarios/auth-subject-link.test.ts +20 -1
  19. package/src/scenarios/discovery.test.ts +12 -0
  20. package/src/scenarios/identity-owner-shape.test.ts +7 -3
  21. package/src/scenarios/owner-subject-echo.test.ts +127 -0
  22. package/src/scenarios/owner-subject-shape.test.ts +96 -0
  23. package/src/scenarios/protocol-versions-array.test.ts +103 -0
  24. package/src/scenarios/webhook-signed-delivery.test.ts +22 -0
  25. package/src/lib/capabilities-auth-subject-link.test.ts +0 -103
  26. package/src/lib/fork-availability.test.ts +0 -69
  27. package/src/lib/global-setup.test.ts +0 -76
  28. package/src/lib/grpc-framing.test.ts +0 -96
  29. package/src/lib/oidc-issuer.test.ts +0 -328
  30. package/src/lib/otel-collector-grpc.test.ts +0 -191
  31. package/src/lib/otel-collector.test.ts +0 -303
  32. package/src/lib/otlp-protobuf.test.ts +0 -461
  33. package/src/lib/polling.test.ts +0 -80
  34. package/src/lib/requirement-ids.test.ts +0 -83
  35. package/src/lib/requirement-ledger.test.ts +0 -75
  36. package/src/lib/risk-disposition.test.ts +0 -91
  37. package/src/lib/saml-idp.test.ts +0 -127
  38. package/src/lib/spec-coherence-registry.test.ts +0 -155
  39. package/src/lib/webhook-receiver.test.ts +0 -144
  40. package/src/scenarios/artifact-schema-compile-bounded.test.ts +0 -126
  41. package/src/scenarios/artifact-type-legacy-ids.test.ts +0 -124
  42. package/src/scenarios/capability-example-root-layout.test.ts +0 -272
  43. package/src/scenarios/certification-floor-enforcement.test.ts +0 -204
  44. package/src/scenarios/chain-subchain-unsupported-refused.test.ts +0 -70
  45. package/src/scenarios/compensation-profile.test.ts +0 -340
  46. package/src/scenarios/core-manifest-and-extension-registry.test.ts +0 -250
  47. package/src/scenarios/discovery-canonical-family-no-shadow.test.ts +0 -219
  48. package/src/scenarios/edge-condition-truthy-falsy.test.ts +0 -108
  49. package/src/scenarios/effect-identity-composition.test.ts +0 -129
  50. package/src/scenarios/effect-identity-cross-scope.test.ts +0 -82
  51. package/src/scenarios/error-envelope-canonical-shape.test.ts +0 -64
  52. package/src/scenarios/form-content-packs.test.ts +0 -415
  53. package/src/scenarios/multi-region-effect-vocabulary.test.ts +0 -175
  54. package/src/scenarios/normative-example-extraction.test.ts +0 -242
  55. package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +0 -309
  56. package/src/scenarios/pack-manifest-extensions.test.ts +0 -203
  57. package/src/scenarios/protocol-version-grammar.test.ts +0 -119
  58. package/src/scenarios/registry-declarative-kinds.test.ts +0 -121
  59. package/src/scenarios/rfc-0147-self-audit.test.ts +0 -104
  60. package/src/scenarios/rfc-lifecycle-coherence.test.ts +0 -215
  61. package/src/scenarios/semantic-digest-v2.test.ts +0 -128
  62. package/src/scenarios/spec-corpus-validity.test.ts +0 -1727
  63. package/src/scenarios/spec-section-citations.test.ts +0 -132
  64. package/src/scenarios/tool-result-trust-monotone.test.ts +0 -168
  65. package/src/scenarios/versioned-composition-profiles.test.ts +0 -201
  66. package/src/scenarios/workflow-chain-internal-flag.test.ts +0 -84
  67. package/src/scenarios/workload-identity-profile.test.ts +0 -184
@@ -1,5 +1,102 @@
1
1
  {
2
- "_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md \u00a7\"Resolving the contract\". Compare against the stamp in your installed @openwop/openwop-conformance to detect a stale hand-copied contract.",
3
- "suiteVersion": "1.153.0",
4
- "corpusCommit": "6c84ef16e2cae24b7e7b8d8bf09b9f46634bf9a5"
2
+ "_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md §\"Resolving the contract\". Compare against the stamp in your installed @openwop/openwop-conformance to detect a stale hand-copied contract.",
3
+ "suiteVersion": "1.155.0",
4
+ "corpusCommit": "69a420b081aac0fc7f3a6b7576855d6d1ba33f48",
5
+ "files": {
6
+ "api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
7
+ "api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
8
+ "api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
9
+ "api/openapi.yaml": "39081c59fb696159806b0f2f9a42e7e9ff830d622fcf2ad4159b21357580a955",
10
+ "api/redocly.yaml": "9f2374405b9ca58994ae076ada9fbe4dfb8265b77e4bf65331d1493192c0bd3a",
11
+ "schemas/README.md": "3769a5bcdc2d8f02c819d20eecf34e0c50666b47ac922380a9b0e0ec13cfc9e2",
12
+ "schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
13
+ "schemas/a2ui-surface-delta-frame.schema.json": "970d434e4fdcb925de64aaace91eb83bfbcfd8f76757a9ce9f339eda5fb3f4e8",
14
+ "schemas/agent-deployment-transition.schema.json": "1ac987fed0f380fa5b4e9a40e8e13316e5ae42d03a0ff42db4be6273e1bfe9f4",
15
+ "schemas/agent-deployment.schema.json": "0a71f0c7ab8cdf4fcac15e8652514f926cd84dfc7db32c47c9f3f3c9bd04f512",
16
+ "schemas/agent-eval-suite.schema.json": "d1aea50a48ddfe5ba8e25e9ddcc2c6e28eb1d953ef81b103518999a50e0967f7",
17
+ "schemas/agent-inventory-response.schema.json": "3a64dde3b95ece16ad73b95051ca99a1ca285f60255b5b506bed655676d82687",
18
+ "schemas/agent-manifest.schema.json": "b82738459792e24b14c4fe43622e8feb0df2250bda02054caa960c3556438b2c",
19
+ "schemas/agent-org-chart.schema.json": "2ca433503e76b29a70be35db22cce24bb698e6aa2f8b79c6fe6025f988f94fb0",
20
+ "schemas/agent-ref.schema.json": "b411bd51439e26d862393d546215d6b26d9349e6c9bdd92b353ca0fde2a51fb2",
21
+ "schemas/agent-roster-entry.schema.json": "3e0d8dfc0c66afb887bd05974a1ffbf79eece391255c09d2d0f7c5536aa219fd",
22
+ "schemas/agent-roster-response.schema.json": "1f6057bddd95d1cd31fdcef2fc87ae4560003c6e0c6d9e3fd1a148bac537c0b8",
23
+ "schemas/ai-envelope.schema.json": "78167a5767809286071bfcfcdfeceed0ed04c8b9f8c13079019d70eff4f351c1",
24
+ "schemas/annotation-create.schema.json": "567e8327777968e0ddab8e1547b4acd7beda734cf9d1fc6cde17037d8d9540d4",
25
+ "schemas/annotation.schema.json": "b26d22f9a97e1ae7c195e8799964aca804e78452a079ca3a0d61ea013907ce91",
26
+ "schemas/artifact-type-pack-manifest.schema.json": "13932ea58e819e01b3a0cc7a65590b70683dd5fe9208fc06c64c339943d3c4ce",
27
+ "schemas/audit-verify-result.schema.json": "e8fd013fb610ba5db27ab0113ec2eb18e7da5f9310dd3de2dd3dc87a259c21fe",
28
+ "schemas/budget-policy.schema.json": "3d4fe30ae129fe136ab9ece395f2a2a42a76360786b1b9a1d5f1cbc753eb5b39",
29
+ "schemas/capabilities.schema.json": "72811aa52360e9b84988e7989e4cad519acf0db096f4e3894574f7d022e37e7a",
30
+ "schemas/certification-bundle-v2.schema.json": "3540232676086869ae78d24f3b9c2154f9c4a8a5eeb976512d6a7409f8cf19bb",
31
+ "schemas/channel-presence-payload.schema.json": "f9d0ef4219814c2b765baf3e68cbe5eea2cb42379f516a93a0459dcd5f7db47e",
32
+ "schemas/channel-written-payload.schema.json": "54723a56bc5a35232f64544ca686ac1ac959a5fb05b32b2401facca6b3ef51a3",
33
+ "schemas/chat-card-pack-manifest.schema.json": "402f7e9081cc39857a52e772061f4e034a957dcbc17501fe6e8e0bcb3d337e42",
34
+ "schemas/compact-tool-descriptor.schema.json": "4856d52aed0438d366b4328e53e4d248126e46f57137ad25fb04c3cc22bba1be",
35
+ "schemas/compensation-policy.schema.json": "6038bf939a982be1105d92b8a0bb70fae936b49d5e8187e1215660d265e2c382",
36
+ "schemas/conformance-certification-bundle.schema.json": "30a1f7fc35ed06098b7abb6fcf3b6f46b5716f29fbae35e0de1247f1f8cbe54c",
37
+ "schemas/connection-pack-manifest.schema.json": "35aca9fc3b3c8322babebe77ef77a8033673254f948025fb2f37191f4cfd05b5",
38
+ "schemas/conversation-event.schema.json": "bfcb88ce8dfe4dd7a3a40e56519a2833c7d25487fd2ab4104f6a23195120c0d9",
39
+ "schemas/conversation-turn.schema.json": "7c025df336aef9456d07f3a1a6959fc6d1c6fa62859e884354d71efee705ca3c",
40
+ "schemas/core-conformance-mock-agent-config.schema.json": "2253e1fb0cd8823ef2bfbae9b957ac374a7b617f62c1eb15d819e3536296feb3",
41
+ "schemas/credential-provenance.schema.json": "7675889469a67c68f31b8d0b28fd0b0a3c837f21ef51ce3b3c676064d3f58597",
42
+ "schemas/credential-reference.schema.json": "14927f095c235367a0645cc4811f4cfcfa3bed152bf05b970881831c62178b56",
43
+ "schemas/debug-bundle.schema.json": "8bd626e4cfbdaa774231e7182f61f2b5a4a42c3afeb8672c8388ea41e3fb7f2e",
44
+ "schemas/dispatch-config.schema.json": "131767797315e980f182b2480a1e2ed562d3326a2db08628704273f5b38c9e26",
45
+ "schemas/envelopes/clarification.request.schema.json": "3b3520a9ca2e40b4fb54b3a8741b2d3f2d414a0dfc004ba7c71dcf3786c32603",
46
+ "schemas/envelopes/error.schema.json": "845261720e8398975042ddfe78012491e42f8a39ed195b524c389cc9bc25f5c0",
47
+ "schemas/envelopes/media.audio.schema.json": "6c23f7398558ed781a36295c980a7ca9c585cc51faac8dd0ea56973be9dc96df",
48
+ "schemas/envelopes/media.file.schema.json": "3eb9ba4d3727a86640d82d566dca4fbcc06f6f0f3f80cfd100be869e9739d0b4",
49
+ "schemas/envelopes/media.image.schema.json": "1517b2036450226a20a55c223583d3cb2b606d157698a0806e00ca9ea1ea413e",
50
+ "schemas/envelopes/schema.request.schema.json": "16f6523778bdae67c715c10b0eb61fb8c7abaed4673d888957d6cc46f604be8c",
51
+ "schemas/envelopes/schema.response.schema.json": "35323b628db30a2b51efb66192f8f1219e184afa130126395524165a98539c6b",
52
+ "schemas/envelopes/ui.a2ui-surface.schema.json": "68f977c11b9bb40435a9c5727b66d76275f8dcfa0b380086622a837c84b8c5e4",
53
+ "schemas/error-envelope.schema.json": "1f2addeabbaa3cd7957c77816a1609b0c5005e0730cff78162bbc2804ff7c4aa",
54
+ "schemas/eval-summary.schema.json": "cda7ea443069f5cfd088cd000dccb4ea8b6e8268cfcc3af740295241bf77fa10",
55
+ "schemas/export-bundle.schema.json": "1ed674786293fc33f3474fb93cd3e5a1d8f85d0cb09636a09856bd8671e62c90",
56
+ "schemas/form-content-pack-manifest.schema.json": "bd373116c8912e2cd8a0e4df1f7a56086c0fdae472320f695552dd46b1b48a45",
57
+ "schemas/frontend-plugin-manifest.schema.json": "33d4e4eea5753900669c7ed6416e314376395a0256a9d656bf03a18845c16e84",
58
+ "schemas/goal.schema.json": "66aab339cda217318dab40e40ea0b46ea949164e7f2482e865284d6b2e3383db",
59
+ "schemas/heartbeat-evaluated.schema.json": "d3caa3fae3d935cc782c2359afa2d6bf11feb1ce9d71b9345b8e5716a9348c61",
60
+ "schemas/heartbeat-state-changed.schema.json": "6b66086582436ac72bd929d2830d0539d932a41b8ddf90263e2b8ca903d60584",
61
+ "schemas/localized-content-language-settings.schema.json": "6ba0e2918e08756493648a64d6169829eed0ead91fb6c3f2a76dec5c451b2d77",
62
+ "schemas/localized-content-page-response.schema.json": "3fd47db2484c40417ab7a555ec42bf443d48deca0c38ecdbe008a3bdcd1a6809",
63
+ "schemas/localized-content-page.schema.json": "2ca2d591d197562c4bf668b338e47803256cfa0c9105abdf64bd79e8469f15a8",
64
+ "schemas/localized-content-section.schema.json": "3e183eb2e80b985dc9b3770e97865247059d32ffbab2957f4a5c0addfdfedc2e",
65
+ "schemas/memory-entry.schema.json": "f5d806c934e900a979e14ccd961224d55eede954ffd10c5369f4cb97654e142b",
66
+ "schemas/memory-list-options.schema.json": "fd3e61c0caf984c6cf14f8acf55c0e84b6b579d62a702be45e20dbedfc9fffce",
67
+ "schemas/node-pack-manifest.schema.json": "3c7812fc8aae840edd8afe6d51fcb58571a50008e0b7ade4b7c5d55e8da9fcb8",
68
+ "schemas/orchestrator-decision.schema.json": "9dbcc9e7595df1781db91b1e8764fec67feca7d232b07d151094400aa53750e7",
69
+ "schemas/org-chart-responsibility-view.schema.json": "9bf2cc7b2ac8b6d7c28d360e8263da5706f691c364c30d20f16fd46b21fdd278",
70
+ "schemas/pack-lockfile.schema.json": "e6c6fd6c042485a3a16c5aedf184a3304b3068c6dd0cf8a22cba1e2e58781556",
71
+ "schemas/prompt-kind.schema.json": "fcf4305b430dc9d2d34b6f2971f4185fd2ed7f617013720d975bfc3ab665e000",
72
+ "schemas/prompt-pack-manifest.schema.json": "d473d827797ce6c9fe4fe03e439840a9dbc78d899bfb9ff05ff3e6a747380977",
73
+ "schemas/prompt-ref.schema.json": "2fffd62cb27c36ddfa32363922b2cb5339091716112c54ded32c21a8c26398bb",
74
+ "schemas/prompt-template.schema.json": "54fc1f0d22b938d171dff214f3d446ae911ef0c3cdb2056929b93d01248e0969",
75
+ "schemas/proposal.schema.json": "472ac8c6bcbb4e34ae8fe7a0757567de4875e69aa5058dac4dc9226ddba6c731",
76
+ "schemas/registry-version-manifest.schema.json": "437890063087beab3cadbdb0dae354e9f92a3ebbecdd90a1d0487df71d34ed93",
77
+ "schemas/residency.schema.json": "b45fdc91ae88091251914dbd6787a1e2ce687580b91d31a759217b7a1f0010c0",
78
+ "schemas/run-ancestry-response.schema.json": "963a5763b03216aa72fc2854560c778e53994f8cb06ea9a4053e541273c87f5c",
79
+ "schemas/run-diff-response.schema.json": "d9d196730b5d5d8a04a591d21a8fc6b44be7fa134f11827ff7e4f1a86d47e83d",
80
+ "schemas/run-event-payloads.schema.json": "37cdbdb5038e148056c104a49b8d28a9668479e06d401309d354a8b0a70921a3",
81
+ "schemas/run-event.schema.json": "c70c06932b9831295042836213e617c8e946f20ddd49cf4e889b74d89d2175a8",
82
+ "schemas/run-options.schema.json": "f8241ece6ddc6527de9160d811d42c231b4f77b935f2e107b3c05608545c59ab",
83
+ "schemas/run-orchestrator-decided-event.schema.json": "17a43a3c44680a24d411141357a867b3caed6107d56de88fe2e062eaf93fa2c1",
84
+ "schemas/run-snapshot.schema.json": "8a45f997beec0a2fa596f4631129a869cfbc37c9ad38583530ed11655301d2ab",
85
+ "schemas/security-advisory.schema.json": "9c550f0bf10a3193dcca4a127081fb66fe5dc1b6f729c4c17bfaea3ba3ce05fc",
86
+ "schemas/self-hosted-runner-dispatch-frame.schema.json": "3f1d7edd572c6747657299525d8e85329b93b6fadf670e3a19c74d53e725adec",
87
+ "schemas/self-hosted-runner-registration.schema.json": "6c781adf1bb915a9e47796494f4a527353cbbf72b480e5a05be4aac65a6d0c6e",
88
+ "schemas/self-hosted-runner-result-frame.schema.json": "16f2f711b6071c797d8d4065f6f5ff39102ff03890fefce7a88a669831566296",
89
+ "schemas/subject.schema.json": "89679e35c7ec586adf6dd4adaff0bfa5453648d067fb2a43d7a2f7b2d016e10c",
90
+ "schemas/suspend-request.schema.json": "1aa39f2a947934bedc9f9696bf45fcb6119395c580d5ca282efb9443bd94a7bb",
91
+ "schemas/tool-descriptor.schema.json": "7f6adbae63003c1f1311f6a436f7f286542be62aa19a27b46730f2c2810857f7",
92
+ "schemas/trigger-event.schema.json": "59d63b79dca3593f45d281776cbfe43ae4d85ee25519b82ad59f162a9efb592c",
93
+ "schemas/trigger-subscription-registration.schema.json": "929d628bad08e1def696e47e9a148aba00fa801e28d49bbd0dc2360b08673435",
94
+ "schemas/trigger-subscription.schema.json": "4f7b067f20032073b140c4bb15dc9a9914c4f6421a252ff1ff261cbdd8edf270",
95
+ "schemas/ui-plugin-message.schema.json": "346638babf1f8ea7b161cde75c0038b518cd2cc5bf0631772e20a9b74c96b5dd",
96
+ "schemas/workflow-chain-pack-manifest.schema.json": "c027571913c44d8d9678750c72701a710a02fa0df55e86849da03e13e95c333a",
97
+ "schemas/workflow-definition.schema.json": "28b050ffe22121796c9d4a5a8c7e57c901bb283bd08d3fd3ff2ea25a898108c1",
98
+ "schemas/workload-identity.schema.json": "64ed93e12cc9f0657e56ff9b8f045e5d68d397df5276d103c07f42c7f34f0579",
99
+ "schemas/workspace-file-create.schema.json": "70a23b92c2958c0165d7d29aea9020a045dc7816af82c80c5f50d0aac19c4856",
100
+ "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4"
101
+ }
5
102
  }
package/schemas/README.md CHANGED
@@ -71,6 +71,7 @@
71
71
  | `run-options.schema.json` | `run-options.md` | Per-run input overlay (configurable + tags + metadata) on `POST /v1/runs` |
72
72
  | `run-orchestrator-decided-event.schema.json` | orchestrator RFC + `observability.md` | Event payload for orchestrator decisions |
73
73
  | `run-snapshot.schema.json` | `rest-endpoints.md` §RunSnapshot | Projected run state from `GET /v1/runs/{runId}` |
74
+ | `subject.schema.json` | `auth.md` §"The Subject record" + RFC 0165 | DRAFT — issuer-scoped, lane-typed identity record carried as `owner.subject` on `RunSnapshot` and the `run.started` echo; opaque `subjectId` (no `@`/whitespace), `keyClass` iff saml/scim, optional bounded `actor` chain. Optional in v1.x; the v2 major requires it. |
74
75
  | `residency.schema.json` | RFC 0129 + `capabilities.md` §dataResidency | OPTIONAL data-residency `{region}` constraint on `POST /v1/runs` (honor-or-reject admission) |
75
76
  | `credential-provenance.schema.json` | `host-capabilities.md` §"Credential provenance + egress policy" (RFC 0079) | Metadata about a host-issued credential at the tool/egress boundary — `credentialId`/`issuer`/`audiences`(+scopes/expiry/redaction/audit-correlation). Secret-free (SR-1); the §C audience-binding MUST is evaluated against `audiences`. |
76
77
  | `security-advisory.schema.json` | `registry-operations.md` + INCIDENT-RESPONSE runbook | Registry-owned CVE advisory record at `registry/security/advisories.json`. One entry per disclosed vulnerability — id, severity, affected pack-name + SemVer range, optional fixedIn/advisoryUrl/credits. Enforced by `check-advisories.mjs` in `.github/workflows/registry-publish.yml`. |
@@ -16,6 +16,13 @@
16
16
  "description": "RFC 0149 \u00a7C \u2014 openwop protocol version the server speaks, as ASCII `<major>.<minor>` with no leading zero except zero itself. `1.0` and `1.12` are valid; `1`, `1.0.0`, `v1.0`, and `01.0` are not. The integer major is the hard compatibility boundary and the integer minor is the additive contract level; PATCH belongs to suite and SDK versions, not the spec version. Previously constrained only by `minLength: 1`, which admitted `v1.0`, `1.0.0`, and `banana` \u2014 a negotiation the wire could not decide, since neither integer can be extracted from an unconstrained string. Independent of `engineVersion` (which gates persisted-doc compatibility per version-negotiation.md).",
17
17
  "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
18
18
  },
19
+ "protocolVersions": {
20
+ "type": "array",
21
+ "minItems": 1,
22
+ "uniqueItems": true,
23
+ "items": { "type": "string", "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$" },
24
+ "description": "RFC 0165 \u00a7A. Every protocol `<major>.<minor>` this host speaks, newest first by convention. OPTIONAL in v1.x; when present it MUST contain the value of `protocolVersion` and MUST NOT name a major the host does not serve. Same grammar as `protocolVersion` (RFC 0149 \u00a7C), not the looser A2A item pattern. Consumers that do not understand it ignore it; profile derivation reads `protocolVersion` only. Reserved for v2 major negotiation."
25
+ },
19
26
  "contractProvenance": {
20
27
  "type": "object",
21
28
  "description": "RFC 0146 (`Active`). Which corpus revision this host's contract handling corresponds to \u2014 the copy it validates and serves against. OPTIONAL; absent \u21d2 UNSPECIFIED provenance, neither current nor stale, and a consumer MUST NOT infer a version from its absence. ADVISORY: a consumer MUST NOT reject a request, refuse interop, or fail a run solely because this differs from its own \u2014 v1.x corpus revisions are additive, so a host on an older revision is conformant and this field detects drift rather than making drift an error. Nothing else on the wire answers this: `protocolVersion` is `1.0` across the entire v1 line (the drift happens inside that value) and `schemaVersions` is per-envelope-type. NOT an integrity check \u2014 `corpusCommit` says WHICH contract, never whether the copy was modified.",
@@ -1019,6 +1019,15 @@
1019
1019
  "principal": {
1020
1020
  "type": "string",
1021
1021
  "minLength": 1
1022
+ },
1023
+ "principalKind": {
1024
+ "type": "string",
1025
+ "enum": ["user", "agent", "anonymous"],
1026
+ "description": "RFC 0132 (declared here by RFC 0165): the echo carries the same optional kind as `RunSnapshot.owner.principalKind`, so a host that echoes its own snapshot owner emits a valid payload."
1027
+ },
1028
+ "subject": {
1029
+ "$ref": "subject.schema.json",
1030
+ "description": "RFC 0165 \u00a7B. The run's Subject record, echoed verbatim from `RunSnapshot.owner.subject` when present."
1022
1031
  }
1023
1032
  },
1024
1033
  "additionalProperties": false
@@ -46,6 +46,10 @@
46
46
  "tenant": { "type": "string", "minLength": 1, "description": "Top-level isolation boundary." },
47
47
  "workspace": { "type": "string", "minLength": 1, "description": "Optional sub-tenant within the tenant (RFC 0048 workspace)." },
48
48
  "principal": { "type": "string", "minLength": 1, "description": "Acting identity (user or agent) — opaque id, never PII." },
49
+ "subject": {
50
+ "$ref": "subject.schema.json",
51
+ "description": "RFC 0165 \u00a7B. OPTIONAL issuer-scoped Subject record for the acting identity. When present: `subject.tenant` MUST equal `tenant`, `subject.subjectId` MUST equal `principal` when both are present, `subject.kind` MUST equal `principalKind` when both are present. A host that emits it on new runs synthesizes `issuer: \"urn:openwop:legacy\"` for runs that predate emission (\u00a7B.3) and copies it verbatim onto forks (\u00a7B.4)."
52
+ },
49
53
  "principalKind": {
50
54
  "type": "string",
51
55
  "enum": ["user", "agent", "anonymous"],
@@ -0,0 +1,56 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/subject.schema.json",
4
+ "title": "OpenWOP Subject",
5
+ "description": "RFC 0165 §B. An issuer-scoped, lane-typed identity record carried as `owner.subject` on RunSnapshot and on the run.started owner echo. With `issuer` in the key, two trust roots issuing the same identifier are distinct by construction. Optional in v1.x; the v2 major requires it and removes the bare `owner.principal` string.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["issuer", "subjectId", "tenant", "lane", "kind"],
9
+ "properties": {
10
+ "issuer": {
11
+ "type": "string",
12
+ "minLength": 1,
13
+ "maxLength": 1024,
14
+ "pattern": "^\\S+$",
15
+ "description": "Trust root that asserted this subject: the SAML IdP entityID, the OIDC `iss`, the mTLS CA subject, the API-key realm, the SCIM connection id, or `urn:openwop:legacy` for a run that predates subject emission (RFC 0165 §B.3)."
16
+ },
17
+ "subjectId": {
18
+ "type": "string",
19
+ "minLength": 1,
20
+ "maxLength": 512,
21
+ "pattern": "^[^@\\s]+$",
22
+ "description": "Opaque, stable, issuer-scoped identifier. MUST NOT be an email address, a display name, or credential material (SECURITY invariant `subject-record-opaque`). Equals `owner.principal` when both are present (RFC 0165 §B.2)."
23
+ },
24
+ "tenant": {
25
+ "type": "string",
26
+ "minLength": 1,
27
+ "description": "MUST equal the enclosing `owner.tenant` (RFC 0165 §B.2)."
28
+ },
29
+ "lane": {
30
+ "type": "string",
31
+ "enum": ["api-key", "oauth2", "oidc", "mtls", "saml", "scim", "ldap", "workload"],
32
+ "description": "The authentication lane that minted the subject (auth-profiles.md). `workload` corresponds to RFC 0154's workload identity; the RFC 0154 `scheme` is carried in `issuer`."
33
+ },
34
+ "kind": {
35
+ "type": "string",
36
+ "enum": ["user", "agent", "anonymous", "workload"],
37
+ "description": "Superset of `owner.principalKind` (RFC 0132) plus `workload` (RFC 0154). Equals `principalKind` when both are present."
38
+ },
39
+ "keyClass": {
40
+ "type": "string",
41
+ "enum": ["opaque-idp", "configured-immutable"],
42
+ "description": "How `subjectId` was derived when the lane is linkable (RFC 0163 §A). Present iff `lane` is `saml` or `scim`; MUST equal `capabilities.auth.subjectLinkKey` when both identity profiles are advertised (RFC 0164 §A.3)."
43
+ },
44
+ "actor": {
45
+ "$ref": "#",
46
+ "description": "Who acted on this subject's behalf (delegation; RFC 0154 §B inverted to the run's point of view). Provenance, not authorization. Chain depth MUST NOT exceed 4; a host refuses rather than truncates (RFC 0165 §B.5)."
47
+ }
48
+ },
49
+ "allOf": [
50
+ {
51
+ "if": { "properties": { "lane": { "enum": ["saml", "scim"] } } },
52
+ "then": { "required": ["keyClass"] },
53
+ "else": { "not": { "required": ["keyClass"] } }
54
+ }
55
+ ]
56
+ }
package/src/cli.ts CHANGED
@@ -195,7 +195,7 @@ Required (unless --offline):
195
195
  --api-key <key> Bearer-style API key (or set OPENWOP_API_KEY env var)
196
196
 
197
197
  Filtering:
198
- --offline Run only the server-free subset (fixtures + spec corpus)
198
+ --offline Run only the declared server-free subset (fixtures-valid; see README §"--offline")
199
199
  --filter <pattern> Pass through to vitest --testNamePattern
200
200
 
201
201
  Implementation labels (cosmetic — surface in failure messages):
@@ -656,10 +656,12 @@ async function main(): Promise<never> {
656
656
  // the conformance package is used as a workspace member.
657
657
  const vitestArgs: string[] = ['run', '--config', resolvePath(conformanceRoot, 'vitest.config.ts')];
658
658
  if (args.offline) {
659
- vitestArgs.push(
660
- 'src/scenarios/fixtures-valid.test.ts',
661
- 'src/scenarios/spec-corpus-validity.test.ts',
662
- );
659
+ // Suite 1.154.0: the offline set is a DECLARED property of the package —
660
+ // exactly the server-free scenarios that ship in the tarball and run in
661
+ // the published layout. `spec-corpus-validity.test.ts` left the set: it is
662
+ // a corpus-coherence scenario (reads spec/v1, asserts nothing about a host)
663
+ // and is no longer packed. See conformance/README.md §"--offline".
664
+ vitestArgs.push('src/scenarios/fixtures-valid.test.ts');
663
665
  }
664
666
  if (args.filter) {
665
667
  vitestArgs.push('--testNamePattern', args.filter);
@@ -56,6 +56,8 @@ const MAX_ROWS = 40;
56
56
  // at runtime, so this costs nothing and makes the compiler the oracle: rename
57
57
  // a ledger field and this file stops building instead of silently degrading.
58
58
  import type { LedgerEntry } from './lib/requirement-ledger.js';
59
+ import { LAYOUT, PKG_ROOT_PATH } from './lib/paths.js';
60
+ import { describeVerdict, verifyCorpusStamp } from './lib/corpus-stamp.js';
59
61
 
60
62
  /** What a JSONL line parses to before validation — every field may be absent. */
61
63
  type LedgerLine = Partial<Record<keyof LedgerEntry, unknown>>;
@@ -64,6 +66,17 @@ let ownedDir: string | null = null;
64
66
  let ledgerPath: string | null = null;
65
67
 
66
68
  export function setup(): void {
69
+ // Suite 1.154.0 — the vendored contract must be the one this suite shipped.
70
+ // In the published layout every api/ + schemas/ file is checked against the
71
+ // SHA-256 map pack-vendor.sh wrote into schemas/CORPUS-STAMP.json; a mismatch
72
+ // aborts the run (a suite validating a host against a schema it did not ship
73
+ // is evidence about nothing). Repo layout: nothing vendored, nothing to check,
74
+ // and the log line says so rather than implying a pass.
75
+ const stamp = verifyCorpusStamp(PKG_ROOT_PATH, LAYOUT);
76
+ process.stderr.write(`${describeVerdict(stamp)}\n`);
77
+ if (stamp.kind === 'mismatch') {
78
+ throw new Error('openwop-conformance: refusing to run — schemas/CORPUS-STAMP.json digests do not match the vendored api/ + schemas/ files. Reinstall the package; do not hand-patch vendored contract files.');
79
+ }
67
80
  if (process.env['OPENWOP_DISPOSITION_SUMMARY'] === 'false') return;
68
81
  // `--certify` already routes the ledger to its own report dir and reads it
69
82
  // there. Never take it over — this exists only for the path that had no
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Corpus-stamp digest verification (v2 charter Phase 1, suite 1.154.0).
3
+ *
4
+ * The published suite vendors `api/` and `schemas/` at `prepack` and writes
5
+ * `schemas/CORPUS-STAMP.json` (RFC 0145 G2) — until 1.154.0 that stamp carried
6
+ * only `suiteVersion` + `corpusCommit`, so a tarball whose vendored files had
7
+ * been altered, partially copied, or hand-patched after install still read as
8
+ * "the 1.x contract". That is the generated-surface-drift class the charter's
9
+ * §A names (`@openwop/openwop-conformance@1.138.1` on npm was not the 1.138.1 in
10
+ * the tree), one layer down: the package identified its corpus by a commit
11
+ * hash it could not check.
12
+ *
13
+ * From 1.154.0 the stamp also carries `files`: a map of every vendored path
14
+ * (relative to the package root) to its SHA-256. In the PUBLISHED layout the
15
+ * suite verifies the map at global setup and refuses to run on a mismatch —
16
+ * a suite that validated a host against a schema it did not ship would be
17
+ * evidence about nothing. In the REPO layout (a spec checkout) there is no
18
+ * vendored copy and nothing to verify; the check is skipped and says so.
19
+ *
20
+ * Three outcomes, never folded (OK / FAIL / UNKNOWN): `verified` when every
21
+ * digest matches; `mismatch` (throws) when any file is missing or altered;
22
+ * `not-applicable` when the layout is not published or the stamp predates
23
+ * 1.154.0 (older tarballs cannot be verified and the run says so rather than
24
+ * pretending).
25
+ */
26
+
27
+ import { createHash } from 'node:crypto';
28
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
29
+ import { join, relative, sep } from 'node:path';
30
+
31
+ export interface CorpusStamp {
32
+ readonly suiteVersion?: string;
33
+ readonly corpusCommit?: string;
34
+ /** `<relative path>` → lowercase hex SHA-256. Present from suite 1.154.0. */
35
+ readonly files?: Readonly<Record<string, string>>;
36
+ }
37
+
38
+ export type StampVerdict =
39
+ | { readonly kind: 'verified'; readonly files: number }
40
+ | { readonly kind: 'not-applicable'; readonly reason: string }
41
+ | { readonly kind: 'mismatch'; readonly missing: readonly string[]; readonly altered: readonly string[]; readonly extra: readonly string[] };
42
+
43
+ export const STAMP_RELATIVE_PATH = join('schemas', 'CORPUS-STAMP.json');
44
+
45
+ /** SHA-256 of a file's bytes, lowercase hex. */
46
+ export function sha256File(path: string): string {
47
+ return createHash('sha256').update(readFileSync(path)).digest('hex');
48
+ }
49
+
50
+ /** Every regular file under `dir`, as package-relative POSIX paths, sorted. */
51
+ export function listVendoredFiles(root: string, dirs: readonly string[] = ['api', 'schemas']): string[] {
52
+ const out: string[] = [];
53
+ const walk = (d: string): void => {
54
+ if (!existsSync(d)) return;
55
+ for (const name of readdirSync(d).sort()) {
56
+ const p = join(d, name);
57
+ if (statSync(p).isDirectory()) walk(p);
58
+ else out.push(relative(root, p).split(sep).join('/'));
59
+ }
60
+ };
61
+ for (const d of dirs) walk(join(root, d));
62
+ return out.filter((p) => p !== STAMP_RELATIVE_PATH.split(sep).join('/')).sort();
63
+ }
64
+
65
+ /** Build the `files` map for a package root (used by pack-vendor at prepack). */
66
+ export function digestVendoredFiles(root: string): Record<string, string> {
67
+ const map: Record<string, string> = {};
68
+ for (const rel of listVendoredFiles(root)) map[rel] = sha256File(join(root, ...rel.split('/')));
69
+ return map;
70
+ }
71
+
72
+ /**
73
+ * Compare the stamp's `files` map against the bytes on disk. Pure: reads,
74
+ * never writes, never throws on a mismatch — the caller decides how loud.
75
+ */
76
+ export function verifyCorpusStamp(root: string, layout: string): StampVerdict {
77
+ if (layout !== 'published') {
78
+ return { kind: 'not-applicable', reason: `layout is '${layout}' — the vendored copy exists only in the published package` };
79
+ }
80
+ const stampPath = join(root, STAMP_RELATIVE_PATH);
81
+ if (!existsSync(stampPath)) {
82
+ return { kind: 'not-applicable', reason: 'schemas/CORPUS-STAMP.json is absent — not a packed suite' };
83
+ }
84
+ let stamp: CorpusStamp;
85
+ try {
86
+ stamp = JSON.parse(readFileSync(stampPath, 'utf8')) as CorpusStamp;
87
+ } catch (e) {
88
+ return { kind: 'mismatch', missing: [STAMP_RELATIVE_PATH], altered: [], extra: [] };
89
+ }
90
+ if (stamp.files === undefined) {
91
+ return { kind: 'not-applicable', reason: `stamp ${stamp.suiteVersion ?? '?'} predates per-file digests (suite < 1.154.0); the vendored contract cannot be verified` };
92
+ }
93
+ const expected = stamp.files;
94
+ const onDisk = new Set(listVendoredFiles(root));
95
+ const missing: string[] = [];
96
+ const altered: string[] = [];
97
+ for (const [rel, digest] of Object.entries(expected).sort()) {
98
+ const p = join(root, ...rel.split('/'));
99
+ if (!existsSync(p)) {
100
+ missing.push(rel);
101
+ continue;
102
+ }
103
+ if (sha256File(p) !== digest) altered.push(rel);
104
+ onDisk.delete(rel);
105
+ }
106
+ const extra = [...onDisk].sort();
107
+ if (missing.length > 0 || altered.length > 0 || extra.length > 0) return { kind: 'mismatch', missing, altered, extra };
108
+ return { kind: 'verified', files: Object.keys(expected).length };
109
+ }
110
+
111
+ /** One-line, greppable rendering for the run log. */
112
+ export function describeVerdict(v: StampVerdict): string {
113
+ switch (v.kind) {
114
+ case 'verified':
115
+ return `[openwop-conformance] corpus stamp VERIFIED — ${v.files} vendored api/ + schemas/ files match their SHA-256 digests`;
116
+ case 'not-applicable':
117
+ return `[openwop-conformance] corpus stamp not checked — ${v.reason}`;
118
+ case 'mismatch':
119
+ return (
120
+ `[openwop-conformance] corpus stamp MISMATCH — the vendored contract is not the one this suite shipped ` +
121
+ `(missing ${v.missing.length}, altered ${v.altered.length}, extra ${v.extra.length}): ` +
122
+ [...v.missing.map((f) => `missing ${f}`), ...v.altered.map((f) => `altered ${f}`), ...v.extra.map((f) => `extra ${f}`)].slice(0, 12).join('; ')
123
+ );
124
+ }
125
+ }
@@ -112,9 +112,13 @@ describe('anonymous-actor-shape: capability advertisement (RFC 0132 §B, server-
112
112
  describe('anonymous-actor-shape: owner.principalKind (RFC 0132 §A, server-free)', () => {
113
113
  it('run-snapshot owner accepts principalKind "anonymous" and rejects "guest"', () => {
114
114
  const snap = loadSchema('run-snapshot.schema.json');
115
- const owner = (snap.properties as Record<string, unknown>).owner as Record<string, unknown>;
115
+ // RFC 0165 added an optional `subject` member ($ref subject.schema.json);
116
+ // register it and give the extracted sub-schema its parent's `$id` as the
117
+ // base the relative $ref resolves against.
118
+ const owner = { $id: snap.$id as string, ...((snap.properties as Record<string, unknown>).owner as Record<string, unknown>) };
116
119
  const ajv = new Ajv2020({ strict: false, allErrors: true });
117
120
  addFormats(ajv);
121
+ ajv.addSchema(loadSchema('subject.schema.json'));
118
122
  const validate = ajv.compile(owner);
119
123
  expect(
120
124
  validate({ tenant: 'acme', principal: 'anon:sess-3f9c', principalKind: 'anonymous' }),
@@ -136,6 +140,7 @@ describe('anonymous-actor-shape: audit reuses authorization.decided (RFC 0132 §
136
140
  const payloads = loadSchema('run-event-payloads.schema.json');
137
141
  const ajv = new Ajv2020({ strict: false, allErrors: true });
138
142
  addFormats(ajv);
143
+ ajv.addSchema(loadSchema('subject.schema.json'));
139
144
  ajv.addSchema(payloads, 'payloads');
140
145
  const decided = ajv.getSchema('payloads#/$defs/authorizationDecided');
141
146
 
@@ -24,7 +24,7 @@
24
24
  */
25
25
 
26
26
  import { describe, it, expect } from 'vitest';
27
- import { softSkip } from '../lib/soft-skip.js';
27
+ import { softSkip, seamAbsent } from '../lib/soft-skip.js';
28
28
  import { driver } from '../lib/driver.js';
29
29
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
30
30
 
@@ -190,3 +190,22 @@ describe('auth-subject-link: link-key hygiene (RFC 0159 §A.2 — opt-in)', () =
190
190
  ).toBe(false);
191
191
  });
192
192
  });
193
+
194
+ describe('auth-subject-link: legacy subjects are never linked (RFC 0165 §B.3 — seam-gated)', () => {
195
+ // Invariant `subject-legacy-not-linkable` (witness class: negative-existence,
196
+ // seam-gated). A run that predates `owner.subject` emission carries a synthesized
197
+ // `issuer: "urn:openwop:legacy"`; a SCIM deactivation MUST NOT deny it. The only
198
+ // causable observation is a host seam that mints a legacy-issuer subject and a
199
+ // linked SCIM record; no such seam is specified yet, so this leg records
200
+ // `blocked` honestly rather than a pass (RFC 0148 §A).
201
+ it('a SCIM deactivation does not deny a SAML login whose run subject is legacy-issued', async () => {
202
+ const res = await driver.get('/.well-known/openwop', { authenticated: false });
203
+ const auth = ((res.json as Record<string, unknown>)['auth'] ?? (res.json as { capabilities?: { auth?: unknown } }).capabilities?.auth) as { profiles?: string[] } | undefined;
204
+ const both = Array.isArray(auth?.profiles) && auth.profiles.includes('openwop-auth-saml') && auth.profiles.includes('openwop-auth-scim');
205
+ if (!both) {
206
+ return softSkip('inapplicable', 'host does not advertise both identity profiles (RFC 0164) — the legacy-link rule has no linked pair to protect');
207
+ }
208
+ return seamAbsent('no seam mints a legacy-issuer subject bound to a SCIM record (RFC 0165 §B.3); the negative cannot be caused from outside the host');
209
+ });
210
+ });
211
+
@@ -10,6 +10,7 @@ import { describe, it, expect } from 'vitest';
10
10
  import { driver } from '../lib/driver.js';
11
11
  import { discoveryFamilies } from '../lib/discovery-capabilities.js';
12
12
  import { behaviorGate } from '../lib/behavior-gate.js';
13
+ import { softSkip } from '../lib/soft-skip.js';
13
14
 
14
15
  describe('discovery: /.well-known/openwop', () => {
15
16
  it('returns 200 with required Capabilities fields per capabilities.md §2', async () => {
@@ -42,6 +43,17 @@ describe('discovery: /.well-known/openwop', () => {
42
43
  )).toBeTruthy();
43
44
  });
44
45
 
46
+ it('IF a standard ETag is present, a matching If-None-Match yields 304 (RFC 0165 §C.2 — presence-gated)', async () => {
47
+ const first = await driver.get('/.well-known/openwop', { authenticated: false });
48
+ const etag = first.headers.get('etag');
49
+ if (etag === null) {
50
+ return softSkip('inapplicable', 'host does not send a standard ETag on the discovery document (RFC 0165 §C.2 — SHOULD, presence-gated)');
51
+ }
52
+ expect(etag.trim().length, driver.describe('RFC 0165 §C.2', 'ETag, when present, MUST be a non-empty validator')).toBeGreaterThan(0);
53
+ const second = await driver.get('/.well-known/openwop', { authenticated: false, headers: { 'If-None-Match': etag } });
54
+ expect(second.status, driver.describe('capabilities-change-detection.md §"Cache validators"', 'a matching If-None-Match SHOULD yield 304 Not Modified')).toBe(304);
55
+ });
56
+
45
57
  it('IF Capabilities-Etag is present, it is non-empty and stable within the cache window', async () => {
46
58
  const first = await driver.get('/.well-known/openwop', { authenticated: false });
47
59
  const firstEtag = first.headers.get('capabilities-etag');
@@ -9,8 +9,9 @@
9
9
  * - Positive: `{ tenant }` and `{ tenant, workspace, principal }` validate.
10
10
  * - Negative: missing `tenant` (required), or an unknown property, is rejected.
11
11
  *
12
- * The owner subschema is self-contained (no external $ref), so it compiles
13
- * standalone via ajv.
12
+ * The owner subschema referenced only itself until RFC 0165 added an optional
13
+ * `subject` member ($ref subject.schema.json); that schema is registered on the
14
+ * ajv instance so the owner subschema still compiles standalone.
14
15
  *
15
16
  * @see RFCS/0048-tenant-workspace-principal-identity-model.md
16
17
  * @see schemas/run-snapshot.schema.json properties.owner
@@ -24,6 +25,7 @@ import { SCHEMAS_DIR } from '../lib/paths.js';
24
25
 
25
26
  interface SnapshotSchema {
26
27
  $schema: string;
28
+ $id: string;
27
29
  properties: { owner?: Record<string, unknown> };
28
30
  }
29
31
 
@@ -40,7 +42,9 @@ describe('category: identity owner-triple shape (RFC 0048 §C)', () => {
40
42
  });
41
43
 
42
44
  const ajv = new Ajv2020({ allErrors: true, strict: false });
43
- const ownerSchema = { $schema: snapshot.$schema, ...(snapshot.properties.owner as Record<string, unknown>) };
45
+ ajv.addSchema(JSON.parse(readFileSync(join(SCHEMAS_DIR, 'subject.schema.json'), 'utf8')) as Record<string, unknown>);
46
+ // `$id` is the base the relative `subject.schema.json` $ref (RFC 0165) resolves against.
47
+ const ownerSchema = { $schema: snapshot.$schema, $id: snapshot.$id, ...(snapshot.properties.owner as Record<string, unknown>) };
44
48
  const validate = ajv.compile(ownerSchema);
45
49
 
46
50
  it('positive: tenant-only owner validates', () => {