@stravica-ai/rcf-schemas 0.7.1 → 0.9.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 (23) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/docs/user-story.md +5 -0
  3. package/fixtures/invalid/user-story/us-014-ac-probe-expect-body-contains-whitespace.json +26 -0
  4. package/fixtures/invalid/user-story/us-015-ac-probe-expect-body-matches-whitespace.json +26 -0
  5. package/fixtures/invalid/user-story/us-016-ac-probe-expect-header-name-whitespace.json +27 -0
  6. package/fixtures/invalid/user-story/us-017-ac-probe-expect-header-match-whitespace.json +27 -0
  7. package/fixtures/invalid/user-story/us-018-ac-probe-selector-whitespace.json +24 -0
  8. package/fixtures/invalid/user-story/us-019-ac-superseded-by-missing-note.json +29 -0
  9. package/fixtures/invalid/user-story/us-020-ac-superseded-by-missing-date.json +29 -0
  10. package/fixtures/invalid/user-story/us-021-ac-supersession-note-without-superseded-by.json +28 -0
  11. package/fixtures/invalid/user-story/us-022-ac-superseded-by-not-an-ac-id.json +30 -0
  12. package/fixtures/invalid/user-story/us-023-ac-superseded-on-bad-date.json +30 -0
  13. package/fixtures/invalid/user-story/us-024-ac-supersession-note-whitespace.json +30 -0
  14. package/fixtures/invalid/user-story/us-025-ac-probe-path-only.json +24 -0
  15. package/fixtures/valid/user-story/us-007-ac-probe-minimal.json +5 -4
  16. package/fixtures/valid/user-story/us-009-ac-probe-expect-body-contains.json +27 -0
  17. package/fixtures/valid/user-story/us-010-ac-probe-expect-body-matches.json +26 -0
  18. package/fixtures/valid/user-story/us-011-ac-probe-expect-header-name.json +27 -0
  19. package/fixtures/valid/user-story/us-012-ac-probe-expect-header-match.json +27 -0
  20. package/fixtures/valid/user-story/us-013-ac-probe-selector.json +24 -0
  21. package/fixtures/valid/user-story/us-014-ac-superseded.json +31 -0
  22. package/package.json +1 -1
  23. package/schemas/user-story.schema.json +40 -9
package/CHANGELOG.md CHANGED
@@ -4,6 +4,87 @@ All notable changes to `@stravica-ai/rcf-schemas` are documented in this file.
4
4
 
5
5
  The format roughly follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Pre-1.0 breaking changes are signalled by a minor bump per semver 0.x convention.
6
6
 
7
+ ## 0.9.0 - 2026-10-19
8
+
9
+ Minor release for the rcf-lite FBS-220 supersession round and the FBS-218 gate 4 schema findings. It folds in the 0.8.1 draft, which was never published, and adopts decision 1 from that draft. A retired acceptance criterion can now carry a durable, validating record of what replaced it: `supersededBy` (an AC id), `supersededOn` (a date) and `supersessionNote` (why), which travel together. In the FBS-220 round `rcf define update AC-16505-2 --set supersededBy=...` failed validate because the criterion had no such field and `disposition` accepts only `fixed | template`. A probe must now carry at least one response assertion: a probe with only `path` (optionally `method` or `parityEnv`) asserts nothing, rcf-lite already refuses one at runtime (finding BH-1 on FBS-218), and the schema now states the same contract for every consumer. Three further findings were analysed in the same pass and are recorded under Decisions; none of them changes a schema.
10
+
11
+ Semver: URLs pinned at `v0.6.0`. The supersession fields are additive (three optional properties on the closed `$defs.acceptanceCriterion`, bound to each other by `dependentRequired`) and on their own were drafted as the patch `0.8.1`. The probe rule is a narrowing: a user story whose probe carries no assertion validated under 0.8.0 and is rejected under 0.9.0. Under this repo's rule (`CONTRIBUTING.md` and the `README.md` Versioning section) a narrowing is a minor bump, as 0.8.0 did for the non-whitespace rule and 0.5.0 for the `testPointer` tightening, so the combined release ships as `0.9.0`. No `0.8.1` was tagged or published; its content ships here.
12
+
13
+ ### Breaking
14
+
15
+ - **`user-story.schema.json`**: `$defs.acProbe` gains an `anyOf` of four arms, `{ "properties": { "<field>": true }, "required": ["<field>"] }` for `expectStatus`, `expectHeader`, `expectBody` and `selector`. A probe with none of the four fails with one `required` error per arm and one `anyOf` error, five errors at the probe's instance path. `path` (the request target), `method` (the verb) and `parityEnv` (the environment scope) are not assertions. Each arm declares its property locally because the test loader runs ajv `strictRequired`. The rule counts presence only: an assertion that is present but malformed (for example `expectBody: {}`) fails on its own constraint, not on `anyOf`.
16
+ - **Fixture and test results that changed.** These are the only changes the scripted drift report finds (see Notes):
17
+ - `fixtures/valid/user-story/us-007-ac-probe-minimal.json`: its 0.8.0 bytes go from valid to invalid (five errors). It was a path-only probe, exactly the vacuous probe the rule exists to reject, so the flip is the intended effect, not drift to avoid. Rewritten in place as the smallest probe that still asserts (`path` plus `expectStatus: 200`), valid. The path-only shape moves to the new invalid fixture `us-025`.
18
+ - `fixtures/invalid/user-story/us-009-ac-probe-unknown-property.json`: still invalid, but on six errors instead of one (the five `anyOf` errors plus the original `additionalProperties` on `timeoutMs`), because its probe is also path-only. Bytes unchanged; the 0.7.1 note that each probe fixture fails on exactly one ajv error no longer holds for it, and its six-error list is now pinned by a test.
19
+ - Test `user-story: AC with a minimal probe (path only) validates` (0.7.1) encoded path-only as valid. It is now `user-story: AC with a path-only probe is rejected on anyOf (a probe requires at least one assertion)`, pinning the five errors, next to a new `user-story: AC with a minimal probe (path and expectStatus) validates`.
20
+ - Test `user-story: probe accepts every method in the enum` (0.7.1) paired each method with a path-only probe and would now fail. Each case now carries `expectStatus: 200`, so the loop still tests the method enum alone.
21
+
22
+ ### Added
23
+
24
+ - **`user-story.schema.json`**: new optional `supersededBy` on `$defs.acceptanceCriterion` (`$ref` to the shared `acId`, so `AC-101-1` and `AC-201` both validate and a REQ or lowercase id does not). Presence marks the criterion as retired: it stays in the record as history and consumers stop treating it as live.
25
+ - **`user-story.schema.json`**: new optional `supersededOn` (string, `format: date`, `YYYY-MM-DD`, the `vendorCitation.verifiedOn` shape) and `supersessionNote` (min-length-1 string with `"pattern": "\\S"`, the 0.8.0 non-whitespace rule) on the same def.
26
+ - **`user-story.schema.json`**: `dependentRequired` on `$defs.acceptanceCriterion`: `supersededBy` requires `supersededOn` and `supersessionNote`, and each of those requires `supersededBy`. All three or none; a partial set fails with one `dependentRequired` error per missing field, naming it in `params.missingProperty`.
27
+ - **Fixtures (valid)**: `fixtures/valid/user-story/us-014-ac-superseded.json` (a `fixed`-disposition criterion superseded by its live sibling, which keeps its `disposition`).
28
+ - **Fixtures (invalid)**: `fixtures/invalid/user-story/us-019-ac-superseded-by-missing-note.json`, `us-020-ac-superseded-by-missing-date.json`, `us-021-ac-supersession-note-without-superseded-by.json` (each one `dependentRequired` error), `us-022-ac-superseded-by-not-an-ac-id.json` (`REQ-303`, one `pattern` error), `us-023-ac-superseded-on-bad-date.json` (`17/10/2026`, one `format` error), `us-024-ac-supersession-note-whitespace.json` (two spaces and a newline, one `pattern` error). Each is also invalid under 0.8.0 (unknown property), so the cases pin the new error shape.
29
+ - **Tests**: `test/user-story.test.js` gains 21 cases: back-compat without the fields; the full record; the flat AC id form; the record alongside every other optional AC field; missing note, missing date, and the FBS-220 regression (`supersededBy` alone, both missing fields named); date or note without `supersededBy`; five non-AC ids rejected on `pattern`; four non-dates rejected on `format`; ten whitespace-only notes rejected on `pattern` and an empty note on `minLength`; `disposition: "superseded"` still rejected on `enum`; a `status` field on a criterion still rejected on `additionalProperties`; self-supersession validating at schema level (a consumer rule, pinned so the limit is visible); and one case per new fixture.
30
+ - **`docs/user-story.md`**: the AC field table gains the three fields and a paragraph on why supersession is not a `disposition` value.
31
+ - **Fixtures (invalid)**: `fixtures/invalid/user-story/us-025-ac-probe-path-only.json`, the path-only probe that `us-007` carried from 0.7.1 to 0.8.0, with a `title` naming the rule. It fails on the `anyOf` rule alone (five errors, one reason) and validates under 0.8.0, so it exercises the new constraint alone.
32
+ - **Tests**: `test/user-story.test.js` gains 11 cases for decision 1: a minimal probe (`path` plus `expectStatus`) validates; `path` with each of the four assertion fields alone validates; `path` with `method` and `parityEnv` but no assertion is rejected with the five-error shape; a path-only probe is rejected for every method in the enum; a present but malformed `expectBody` fails on `minProperties`, not on `anyOf`; and one case each for `valid/us-007` (rewritten), `invalid/us-025` and `invalid/us-009` (its six-error list). Total suite grows from 433 to 465: 21 supersession cases and 11 decision 1 cases.
33
+
34
+ ### Changed
35
+
36
+ - **`user-story.schema.json`**: the `description` of `$defs.acProbe`, `path` and `parityEnv` now says a probe needs an assertion and that `path` and `parityEnv` are not assertions (`path` was described as "The only required field").
37
+ - **Tests hardened, results unchanged**: four 0.7.1 rejection cases used a path-only probe, so under 0.9.0 each would also fail on `anyOf` and no longer prove its own constraint. Each now carries `expectStatus: 200` and pins its single error: lowercase `method` (`enum`), empty `path` (`minLength`), unknown `timeoutMs` (`additionalProperties`) and empty `parityEnv` (`minLength`). They pass against both 0.8.0 and 0.9.0.
38
+
39
+ ### Decisions
40
+
41
+ 1. **Probe requires at least one response assertion: adopted.** The rule is right: a probe with only `path` asserts nothing, and rcf-lite already refuses one at runtime (`PROBE_ASSERTION_FIELDS` in `packages/rcf-lite/src/verify/engine/catalog.js`, gate finding BH-1, which resolves it to a fail with `missing assertion`), so the schema states the same contract for every consumer instead of leaving it to one resolver. The 0.8.1 draft held the rule because it cannot ship with zero fixture drift. The release-holder adopted it on 2026-10-19: the drift is the point, since `us-007` is an example of exactly the vacuous probe the rule rejects and `us-009` is the same shape with an unknown property added. Keeping the runtime check as the only enforcement was rejected. Shape and the named drifts are under Breaking above.
42
+ 2. **Match-anything regexes: runtime, not schema.** Nothing is added to the schema. Whether a pattern matches every body is a property of the regex, not of its spelling: a denylist of `.*`, `^.*$`, `[\s\S]*`, `.+`, `^`, `$` and `(?:)` is evaded by `.*?`, `(.*)`, `.{0,}`, `\S*`, `x?`, `a|`, `[^]*` and `.`, so a schema `pattern` or `not.enum` would advertise a guarantee it cannot give. It also could not apply to `expectHeader.match`, which rcf-lite resolves as a substring (`value.includes(match)`), not a regex, so `.*` there is a literal and not vacuous. **Rule for rcf-lite's verify engine:** before resolving `expectBody.matches`, compile the source and test it against two fixed canary strings from disjoint character classes, one of word characters only (for example `qzvxjk0729`) and one of non-word, non-space punctuation only (for example `~!@#%^&*()`). A pattern that matches both asserts nothing about the body: resolve the criterion as a fail with `failedExpectation: "expectBody.matches"` and an expected value naming the vacuity, never a pass; a source that does not compile is also a fail. Every spelling above, named or evasive, matches both canaries; `^\{"notes":\[`, `<h1>\s*Notes\s*</h1>`, `Hello, \w+`, `\w+`, `[0-9a-f]{8}`, `^$` and plain text match neither or one and pass the check. Fixed canaries keep the deterministic mode deterministic. If `expectHeader.match` ever becomes a regex, the same rule applies to it.
43
+ 3. **Criterion-level supersession: `supersededBy` plus a required dated note (option a).** Extending `disposition` with `superseded` (option b) was rejected because `disposition` records who owns the criterion (blueprint or applying agent), a separate axis from whether it is live: a `fixed` criterion that is superseded would lose its owner marker. Both (option c) would give two sources of truth that can disagree. `supersededBy` matches the field the FBS-220 round already reached for and the ADR record's own `supersededBy`. A single successor id is carried; a criterion split in two names its primary successor and the note names the other, and widening to a list later is additive.
44
+ 4. **Packed-artefact profile on VerifyReport v1: not in this repo.** The VerifyReport v1 shape (`schemaVersion: '1'`, `profile`) is defined in rcf-lite (`packages/rcf-lite/src/verify/report/index.js` and `src/verify/profile/index.js`); rcf-schemas carries no verify-report schema. Nothing is added here; the rcf-lite suite-as-probe DEFINE picks up the `packagedArtefact` profile and the `artefact` object.
45
+
46
+ ### Migration
47
+
48
+ - **Existing chains on 0.8.0**: bump `@stravica-ai/rcf-schemas` to `0.9.0`. A document is rejected only when a probe carries none of `expectStatus`, `expectHeader`, `expectBody` or `selector`: add the assertion the criterion actually makes (for a health check, `expectStatus: 200`), or remove the probe so the criterion stays critique-only. The three supersession fields are optional and unknown to pre-0.9.0 callers.
49
+ - **To retire a criterion**: set all three in one write, for example `rcf define update AC-16505-2 --set supersededBy=AC-16505-4 --set supersededOn=2026-10-17 --set supersessionNote="..."`. Setting `supersededBy` alone fails validate by design and names both missing fields.
50
+ - **Consumers to bump**: `rcf-lite` (pins the schemas package exact; bump the pin to `0.9.0`). For decision 1, the runtime refusal in `catalog.js` is now duplicated by the schema, so a path-only probe is rejected wherever rcf-lite validates a user story against `$defs.acProbe`; `catalog.js` may keep its check as belt and braces for any probe that reaches verify without passing schema validation. Any rcf-lite fixture or test that carries a path-only probe flips with the pin and needs an assertion. What rcf-lite must also do, none of which this repo touches: (1) `rcf audit coverage --strict` treats a criterion carrying `supersededBy` as retired: it requires no bound test and leaves the coverage denominator, and a test still bound to it is reported, not counted; (2) the cross-record rules the schema cannot express: the successor exists in the same chain and is not the criterion itself, and following `supersededBy` from any retired criterion ends at a live one (no cycle); (3) deterministic verify skips a retired criterion's probe rather than resolving it; (4) the decision 2 canary rule in the verify engine.
51
+
52
+ ### Notes
53
+
54
+ - Scripted drift report at authoring time: every fixture at main `2341f24` (0.8.0: 72 valid + 100 invalid = 172) and every fixture at the 0.8.1 draft head `973b8fb` (73 valid + 106 invalid = 179), in their original bytes, validated against the 0.9.0 schemas. Exactly two change outcome or error list: `valid/user-story/us-007` (valid to invalid) and `invalid/user-story/us-009` (one error to six). Every other fixture keeps its outcome and its exact ajv error list. The supersession fields alone produced zero drift on the 172.
55
+ - Mutation check at authoring time: the 0.9.0 tests and fixtures run against the 0.8.1 draft schemas (`973b8fb`) fail 6 of 465: the five decision 1 rejection cases (path-only, path-only per method, path with `method` and `parityEnv`, and the `us-025` and `us-009` fixture pins) and the existing `invalid fixtures: user-story` runner, which fails only because the new `us-025` validates there. Run against the 0.8.0 schemas they fail 27 of 465: those 6, the 20 supersession cases that depend on the new fields, and the existing `valid fixtures: user-story` runner on `us-014`. Every acceptance case, every hardened case and every other pre-existing case passes on both.
56
+ - `docs/user-story.md` labels the three supersession rows 0.9.0 and still does not describe `probe` (deferred at 0.7.1).
57
+ - `back-compat-freeze.test.js` continues to pass.
58
+
59
+ ## 0.8.0 - 2026-10-16
60
+
61
+ Minor release for rcf-lite FBS-218 (per-AC probe catalog, rcf-lite PR 370), review gate 3. A probe `{ "path": "/", "expectBody": { "contains": " " } }` validated under 0.7.1, because `minLength: 1` counts a single space, and a deterministic verify run resolved it to a vacuous pass. Every assertion string on the probe must now contain at least one non-whitespace character: `"pattern": "\\S"` alongside the existing `minLength: 1`.
62
+
63
+ Semver: URLs pinned at `v0.6.0`. This is a narrowing, not an addition: a user story whose probe carries a whitespace-only assertion string validated under 0.7.1 and is rejected under 0.8.0. Under this repo's rule (`CONTRIBUTING.md` and the `README.md` Versioning section) a narrowing is a minor bump, as 0.5.0 did for the `testPointer` `minLength: 1` tightening, so this ships as `0.8.0`. The only documents rejected are ones whose assertion string asserts nothing, which is the defect this release closes; rcf-lite pins the package exact and bumps on its FBS-218 branch.
64
+
65
+ ### Added
66
+
67
+ - **Fixtures (valid)**: `fixtures/valid/user-story/us-009-ac-probe-expect-body-contains.json` (`contains: "Welcome back"`), `fixtures/valid/user-story/us-010-ac-probe-expect-body-matches.json` (`matches` with a `\s*` class), `fixtures/valid/user-story/us-011-ac-probe-expect-header-name.json` (`name: "x-request-id"`), `fixtures/valid/user-story/us-012-ac-probe-expect-header-match.json` (`match: "public, max-age=60"`), `fixtures/valid/user-story/us-013-ac-probe-selector.json` (`selector` with a descendant combinator).
68
+ - **Fixtures (invalid)**: `fixtures/invalid/user-story/us-014-ac-probe-expect-body-contains-whitespace.json` (a single space, the gate 3 case), `fixtures/invalid/user-story/us-015-ac-probe-expect-body-matches-whitespace.json` (a tab), `fixtures/invalid/user-story/us-016-ac-probe-expect-header-name-whitespace.json` (two spaces), `fixtures/invalid/user-story/us-017-ac-probe-expect-header-match-whitespace.json` (a newline), `fixtures/invalid/user-story/us-018-ac-probe-selector-whitespace.json` (space, tab, space). Each fails on exactly one ajv error (`pattern`), and each validates under 0.7.1, so it exercises the new constraint alone.
69
+ - **Tests**: `test/user-story.test.js` gains 31 cases: the gate 3 regression probe; for each of the five fields, a whitespace-only value rejected on `pattern` across ten ASCII and Unicode whitespace strings, a real value accepted with inner, leading and trailing whitespace, a one-character value accepted (the `minLength: 1` boundary), and an empty string still rejected on `minLength`; and one case per new fixture, pinning its outcome and, for the invalid ones, the single `pattern` error at its instance path. Total suite grows from 402 to 433.
70
+
71
+ ### Changed
72
+
73
+ - **`user-story.schema.json`**: `$defs.acProbeExpectBody.contains`, `$defs.acProbeExpectBody.matches`, `$defs.acProbeExpectHeader.name`, `$defs.acProbeExpectHeader.match` and `$defs.acProbe.selector` each gain `"pattern": "\\S"` next to `minLength: 1`, and each `description` says so. The pattern is unanchored, so a real value with leading, trailing or inner whitespace still validates. ajv compiles patterns with the `u` flag, so `\S` also treats Unicode whitespace (no-break space, em space, byte-order mark, line separator) as whitespace.
74
+ - **Unchanged on purpose**: `$defs.acProbe.path` and `$defs.acProbe.parityEnv` keep `minLength: 1` only. They name the request target and the environment scope, not something the resolver checks the response against, so they sit outside this fix. `method` (enum), `expectStatus` (integer, 100..599), the `expectBody` exactly-one-of rule and every other def are untouched.
75
+
76
+ ### Migration
77
+
78
+ - **Existing chains on 0.7.1**: bump `@stravica-ai/rcf-schemas` to `0.8.0`. A document is rejected only when a probe carries a whitespace-only `expectBody.contains`, `expectBody.matches`, `expectHeader.name`, `expectHeader.match` or `selector`. Replace the value with the text the criterion actually asserts, or remove that assertion.
79
+ - **Consumers to bump**: `rcf-lite` (its `packages/rcf-lite/package.json` pins the schemas package exact, at `0.7.0` on `main` at this release). FBS-218 validates every probe against `$defs.acProbe`, so pinning `0.8.0` closes the whitespace-only case there without resolver-side trimming. This repo does not touch rcf-lite.
80
+
81
+ ### Notes
82
+
83
+ - Scripted back-compat pass at authoring time: every 0.7.1 fixture at main `c34bf96` (67 valid + 95 invalid = 162 total) validates to the same outcome, with the same ajv error list, against the 0.8.0 schemas: zero drift.
84
+ - Mutation check at authoring time: run against the 0.7.1 `user-story.schema.json`, the 11 new rejection cases fail, as does the existing invalid-fixture case for `user-story`; the 20 new acceptance and `minLength` cases pass on both.
85
+ - `docs/user-story.md` still does not describe `probe` (deferred at 0.7.1); this release is scoped to the schema, fixtures, tests, version and changelog.
86
+ - `back-compat-freeze.test.js` continues to pass.
87
+
7
88
  ## 0.7.1 - 2026-10-16
8
89
 
9
90
  Patch bump for rcf-lite issue 349 (per-AC probe catalog resolved by deterministic verify, rcf-lite TAC-4150 and FBS-218). Adds an additive, optional `probe` object on the inline `$defs.acceptanceCriterion` of the user story, so an acceptance criterion can carry a structured, deterministic probe that `rcf verify run --mode deterministic` resolves to pass, fail or skipped with evidence. An AC without a probe stays critique-only. Every change is additive: every 0.7.0-valid document keeps validating.
@@ -47,6 +47,11 @@ Each AC inside `acceptanceCriteria[]`:
47
47
  | `ownerRef` | object: `{tacId?, adrId?, field?}` | no | 0.6.2. Back-reference naming the owning TAC or ADR the criterion observes rather than restating. `tacId` and `adrId` re-use the shared id patterns; `field` is an optional dotted path into the owning record (for example, `interfaces.principal.fields` or `decision.retryPolicy`) pinpointing the surface. All sub-fields optional at schema level so the record composes additively; consumers may require at least one of `tacId` or `adrId` when the criterion references an owning artefact. `additionalProperties: false`. |
48
48
  | `disposition` | enum: `fixed`, `template` | no | 0.6.2. Marks whether the criterion is the blueprint's to fix (mechanism-invariant, inherited unchanged by every applying project) or the applying agent's to set (project-parameterised, shape-given and value-open). Optional at schema level; consumer rulesets that gate blueprint contributions may require it on every AC of a shipped blueprint. Absence carries no default: a consumer that requires the marker treats an unmarked AC as a defect, and a consumer that does not require it ignores the absence. |
49
49
  | `vendorCitation` | object: `{url, verifiedOn}` | no | 0.6.2. Vendor citation for a criterion whose truth rests on a third-party platform fact. Both sub-fields required when the object is present: `url` is the vendor documentation URL (JSON Schema `format: uri`); `verifiedOn` is the ISO-8601 date (`format: date`, `YYYY-MM-DD`) the fact was verified against that URL. Consumers may fetch the URL at gate time so a dead link is a finding, and may compare `verifiedOn` against a tolerance window. `additionalProperties: false`. |
50
+ | `supersededBy` | `acId` | no | 0.9.0. Id of the criterion that replaces this one. Presence marks the criterion as retired: it stays in the record as history, and consumers stop treating it as live (a coverage audit does not require a bound test for it). Requires `supersededOn` and `supersessionNote`. That the successor exists in the chain, is not this criterion and forms no cycle are cross-record rules for the consumer. |
51
+ | `supersededOn` | string, `format: date` | with `supersededBy` | 0.9.0. ISO-8601 date (`YYYY-MM-DD`) the criterion was superseded. Valid only alongside `supersededBy`. |
52
+ | `supersessionNote` | string, min 1, at least one non-whitespace character | with `supersededBy` | 0.9.0. Why the criterion was retired and how its successor covers it (for example the decision id that ruled the change). Valid only alongside `supersededBy`. |
53
+
54
+ The three supersession fields travel together (`dependentRequired`): all three or none, and a partial set is rejected. Supersession is deliberately not a `disposition` value, because `disposition` records who owns the criterion (blueprint or applying agent), a separate axis from whether it is still live; a blueprint-fixed criterion can be superseded and keeps its `disposition`.
50
55
 
51
56
  The Given/When/Then triple is optional at the schema level so non-Gherkin teams aren't forced into it; the `testable` boolean is required to make authors stop and confirm the AC can actually be tested.
52
57
 
@@ -0,0 +1,26 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "draft",
7
+ "title": "Probe expectBody.contains as a single space is rejected",
8
+ "asA": "author",
9
+ "iWant": "a body assertion that asserts something",
10
+ "soThat": "a whitespace-only contains cannot resolve to a vacuous pass",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "expectBody.contains is a single space.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/",
18
+ "expectBody": {
19
+ "contains": " "
20
+ }
21
+ }
22
+ }
23
+ ],
24
+ "createdAt": "2026-01-01T00:00:00Z",
25
+ "updatedAt": "2026-10-16T00:00:00Z"
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "draft",
7
+ "title": "Probe expectBody.matches as a tab is rejected",
8
+ "asA": "author",
9
+ "iWant": "a body pattern that asserts something",
10
+ "soThat": "a whitespace-only regular expression source cannot resolve to a vacuous pass",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "expectBody.matches is a single tab.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/",
18
+ "expectBody": {
19
+ "matches": "\t"
20
+ }
21
+ }
22
+ }
23
+ ],
24
+ "createdAt": "2026-01-01T00:00:00Z",
25
+ "updatedAt": "2026-10-16T00:00:00Z"
26
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "draft",
7
+ "title": "Probe expectHeader.name as two spaces is rejected",
8
+ "asA": "author",
9
+ "iWant": "a header assertion keyed on a real header name",
10
+ "soThat": "a whitespace-only header name cannot resolve to a vacuous pass",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "expectHeader.name is two spaces.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/",
18
+ "expectHeader": {
19
+ "name": " ",
20
+ "match": "application/json"
21
+ }
22
+ }
23
+ }
24
+ ],
25
+ "createdAt": "2026-01-01T00:00:00Z",
26
+ "updatedAt": "2026-10-16T00:00:00Z"
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "draft",
7
+ "title": "Probe expectHeader.match as a newline is rejected",
8
+ "asA": "author",
9
+ "iWant": "a header assertion with a real expected value",
10
+ "soThat": "a whitespace-only header value cannot resolve to a vacuous pass",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "expectHeader.match is a single newline.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/",
18
+ "expectHeader": {
19
+ "name": "content-type",
20
+ "match": "\n"
21
+ }
22
+ }
23
+ }
24
+ ],
25
+ "createdAt": "2026-01-01T00:00:00Z",
26
+ "updatedAt": "2026-10-16T00:00:00Z"
27
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "draft",
7
+ "title": "Probe selector as mixed whitespace is rejected",
8
+ "asA": "author",
9
+ "iWant": "a DOM assertion on a real selector",
10
+ "soThat": "a whitespace-only selector cannot resolve to a vacuous pass",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "selector is a space, a tab and a space.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/",
18
+ "selector": " \t "
19
+ }
20
+ }
21
+ ],
22
+ "createdAt": "2026-01-01T00:00:00Z",
23
+ "updatedAt": "2026-10-16T00:00:00Z"
24
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "usId": "US-303",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-303",
5
+ "version": "1.1.0",
6
+ "status": "draft",
7
+ "title": "Supersession without a note is rejected",
8
+ "asA": "author retiring a criterion",
9
+ "iWant": "a supersession record that validates",
10
+ "soThat": "a retired criterion cannot lose its successor, date or reason",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-303-1",
14
+ "description": "supersededBy and supersededOn without supersessionNote.",
15
+ "testable": true,
16
+ "scope": "deployed",
17
+ "supersededBy": "AC-303-2",
18
+ "supersededOn": "2026-10-17"
19
+ },
20
+ {
21
+ "id": "AC-303-2",
22
+ "description": "The notes list renders the 25 most recent notes.",
23
+ "testable": true,
24
+ "scope": "deployed"
25
+ }
26
+ ],
27
+ "createdAt": "2026-01-01T00:00:00Z",
28
+ "updatedAt": "2026-10-19T00:00:00Z"
29
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "usId": "US-303",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-303",
5
+ "version": "1.1.0",
6
+ "status": "draft",
7
+ "title": "Supersession without a date is rejected",
8
+ "asA": "author retiring a criterion",
9
+ "iWant": "a supersession record that validates",
10
+ "soThat": "a retired criterion cannot lose its successor, date or reason",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-303-1",
14
+ "description": "supersededBy and supersessionNote without supersededOn.",
15
+ "testable": true,
16
+ "scope": "deployed",
17
+ "supersededBy": "AC-303-2",
18
+ "supersessionNote": "A pagination ruling caps the list at 25; AC-303-2 carries the bound."
19
+ },
20
+ {
21
+ "id": "AC-303-2",
22
+ "description": "The notes list renders the 25 most recent notes.",
23
+ "testable": true,
24
+ "scope": "deployed"
25
+ }
26
+ ],
27
+ "createdAt": "2026-01-01T00:00:00Z",
28
+ "updatedAt": "2026-10-19T00:00:00Z"
29
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "usId": "US-303",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-303",
5
+ "version": "1.1.0",
6
+ "status": "draft",
7
+ "title": "A supersession note without a successor is rejected",
8
+ "asA": "author retiring a criterion",
9
+ "iWant": "a supersession record that validates",
10
+ "soThat": "a retired criterion cannot lose its successor, date or reason",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-303-1",
14
+ "description": "supersessionNote without supersededBy.",
15
+ "testable": true,
16
+ "scope": "deployed",
17
+ "supersessionNote": "A pagination ruling caps the list at 25; AC-303-2 carries the bound."
18
+ },
19
+ {
20
+ "id": "AC-303-2",
21
+ "description": "The notes list renders the 25 most recent notes.",
22
+ "testable": true,
23
+ "scope": "deployed"
24
+ }
25
+ ],
26
+ "createdAt": "2026-01-01T00:00:00Z",
27
+ "updatedAt": "2026-10-19T00:00:00Z"
28
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "usId": "US-303",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-303",
5
+ "version": "1.1.0",
6
+ "status": "draft",
7
+ "title": "Supersession pointing at a non-AC id is rejected",
8
+ "asA": "author retiring a criterion",
9
+ "iWant": "a supersession record that validates",
10
+ "soThat": "a retired criterion cannot lose its successor, date or reason",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-303-1",
14
+ "description": "supersededBy names a requirement, not a criterion.",
15
+ "testable": true,
16
+ "scope": "deployed",
17
+ "supersededBy": "REQ-303",
18
+ "supersededOn": "2026-10-17",
19
+ "supersessionNote": "A pagination ruling caps the list at 25; AC-303-2 carries the bound."
20
+ },
21
+ {
22
+ "id": "AC-303-2",
23
+ "description": "The notes list renders the 25 most recent notes.",
24
+ "testable": true,
25
+ "scope": "deployed"
26
+ }
27
+ ],
28
+ "createdAt": "2026-01-01T00:00:00Z",
29
+ "updatedAt": "2026-10-19T00:00:00Z"
30
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "usId": "US-303",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-303",
5
+ "version": "1.1.0",
6
+ "status": "draft",
7
+ "title": "Supersession with a malformed date is rejected",
8
+ "asA": "author retiring a criterion",
9
+ "iWant": "a supersession record that validates",
10
+ "soThat": "a retired criterion cannot lose its successor, date or reason",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-303-1",
14
+ "description": "supersededOn is not an ISO-8601 date.",
15
+ "testable": true,
16
+ "scope": "deployed",
17
+ "supersededBy": "AC-303-2",
18
+ "supersededOn": "17/10/2026",
19
+ "supersessionNote": "A pagination ruling caps the list at 25; AC-303-2 carries the bound."
20
+ },
21
+ {
22
+ "id": "AC-303-2",
23
+ "description": "The notes list renders the 25 most recent notes.",
24
+ "testable": true,
25
+ "scope": "deployed"
26
+ }
27
+ ],
28
+ "createdAt": "2026-01-01T00:00:00Z",
29
+ "updatedAt": "2026-10-19T00:00:00Z"
30
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "usId": "US-303",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-303",
5
+ "version": "1.1.0",
6
+ "status": "draft",
7
+ "title": "Supersession with a whitespace-only note is rejected",
8
+ "asA": "author retiring a criterion",
9
+ "iWant": "a supersession record that validates",
10
+ "soThat": "a retired criterion cannot lose its successor, date or reason",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-303-1",
14
+ "description": "supersessionNote is two spaces and a newline.",
15
+ "testable": true,
16
+ "scope": "deployed",
17
+ "supersededBy": "AC-303-2",
18
+ "supersededOn": "2026-10-17",
19
+ "supersessionNote": " \n"
20
+ },
21
+ {
22
+ "id": "AC-303-2",
23
+ "description": "The notes list renders the 25 most recent notes.",
24
+ "testable": true,
25
+ "scope": "deployed"
26
+ }
27
+ ],
28
+ "createdAt": "2026-01-01T00:00:00Z",
29
+ "updatedAt": "2026-10-19T00:00:00Z"
30
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "US-301",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-301",
5
+ "version": "1.0.0",
6
+ "status": "draft",
7
+ "title": "Path-only probe is rejected: a probe requires at least one assertion (acProbe anyOf, 0.9.0)",
8
+ "asA": "operator running deterministic verify",
9
+ "iWant": "a probe to carry at least one of expectStatus, expectHeader, expectBody or selector",
10
+ "soThat": "a probe that only names a path cannot resolve to a vacuous pass",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-301-1",
14
+ "description": "Probe names a path and asserts nothing about the response (the 0.7.1 to 0.8.0 us-007 shape, rcf-lite FBS-218 finding BH-1).",
15
+ "testable": true,
16
+ "scope": "deployed",
17
+ "probe": {
18
+ "path": "/healthz"
19
+ }
20
+ }
21
+ ],
22
+ "createdAt": "2026-01-01T00:00:00Z",
23
+ "updatedAt": "2026-10-19T00:00:00Z"
24
+ }
@@ -6,19 +6,20 @@
6
6
  "status": "approved",
7
7
  "title": "Health endpoint answers on the deployed URL",
8
8
  "asA": "operator running deterministic verify",
9
- "iWant": "an acceptance criterion to carry the smallest possible probe",
9
+ "iWant": "an acceptance criterion to carry the smallest probe that still asserts something",
10
10
  "soThat": "rcf verify run resolves it without a critique pass",
11
11
  "acceptanceCriteria": [
12
12
  {
13
13
  "id": "AC-301-1",
14
- "description": "The health endpoint answers on the deployed URL.",
14
+ "description": "The health endpoint answers 200 on the deployed URL.",
15
15
  "testable": true,
16
16
  "scope": "deployed",
17
17
  "probe": {
18
- "path": "/healthz"
18
+ "path": "/healthz",
19
+ "expectStatus": 200
19
20
  }
20
21
  }
21
22
  ],
22
23
  "createdAt": "2026-01-01T00:00:00Z",
23
- "updatedAt": "2026-10-16T00:00:00Z"
24
+ "updatedAt": "2026-10-19T00:00:00Z"
24
25
  }
@@ -0,0 +1,27 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Probe expectBody.contains carries real text with inner whitespace",
8
+ "asA": "operator running deterministic verify",
9
+ "iWant": "a body assertion on literal text that includes spaces",
10
+ "soThat": "a real phrase passes while a whitespace-only string is refused",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "The home page greets the signed-in user.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/",
18
+ "expectStatus": 200,
19
+ "expectBody": {
20
+ "contains": "Welcome back"
21
+ }
22
+ }
23
+ }
24
+ ],
25
+ "createdAt": "2026-01-01T00:00:00Z",
26
+ "updatedAt": "2026-10-16T00:00:00Z"
27
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Probe expectBody.matches carries a real regular expression",
8
+ "asA": "operator running deterministic verify",
9
+ "iWant": "a body assertion on a pattern that includes a whitespace class",
10
+ "soThat": "a real pattern passes while a whitespace-only source is refused",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "The notes page renders its heading.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/notes",
18
+ "expectBody": {
19
+ "matches": "<h1>\\s*Notes\\s*</h1>"
20
+ }
21
+ }
22
+ }
23
+ ],
24
+ "createdAt": "2026-01-01T00:00:00Z",
25
+ "updatedAt": "2026-10-16T00:00:00Z"
26
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Probe expectHeader.name carries a real header name",
8
+ "asA": "operator running deterministic verify",
9
+ "iWant": "a header assertion keyed on a named response header",
10
+ "soThat": "a real header name passes while a whitespace-only name is refused",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "The health endpoint carries a request id header.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/healthz",
18
+ "expectHeader": {
19
+ "name": "x-request-id",
20
+ "match": "^[0-9a-f-]{36}$"
21
+ }
22
+ }
23
+ }
24
+ ],
25
+ "createdAt": "2026-01-01T00:00:00Z",
26
+ "updatedAt": "2026-10-16T00:00:00Z"
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Probe expectHeader.match carries a real value with inner whitespace",
8
+ "asA": "operator running deterministic verify",
9
+ "iWant": "a header assertion whose expected value includes a space",
10
+ "soThat": "a real header value passes while a whitespace-only value is refused",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "The public page is cacheable for a minute.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/",
18
+ "expectHeader": {
19
+ "name": "cache-control",
20
+ "match": "public, max-age=60"
21
+ }
22
+ }
23
+ }
24
+ ],
25
+ "createdAt": "2026-01-01T00:00:00Z",
26
+ "updatedAt": "2026-10-16T00:00:00Z"
27
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "US-302",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-302",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Probe selector carries a real descendant selector",
8
+ "asA": "operator running deterministic verify",
9
+ "iWant": "a DOM assertion with a descendant combinator",
10
+ "soThat": "a real selector passes while a whitespace-only selector is refused",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-302-1",
14
+ "description": "The notes list renders inside the main landmark.",
15
+ "testable": true,
16
+ "probe": {
17
+ "path": "/notes",
18
+ "selector": "main [data-testid=\"notes-list\"]"
19
+ }
20
+ }
21
+ ],
22
+ "createdAt": "2026-01-01T00:00:00Z",
23
+ "updatedAt": "2026-10-16T00:00:00Z"
24
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "usId": "US-303",
3
+ "prdId": "PRD-100",
4
+ "reqId": "REQ-303",
5
+ "version": "1.1.0",
6
+ "status": "approved",
7
+ "title": "Notes list paginates",
8
+ "asA": "author retiring a criterion",
9
+ "iWant": "a retired criterion to keep a durable record of its successor",
10
+ "soThat": "coverage stops asking for a test on it and the history stays auditable",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-303-1",
14
+ "description": "The notes list renders every note.",
15
+ "testable": true,
16
+ "scope": "deployed",
17
+ "disposition": "fixed",
18
+ "supersededBy": "AC-303-2",
19
+ "supersededOn": "2026-10-17",
20
+ "supersessionNote": "A pagination ruling caps the list at 25; AC-303-2 carries the bound."
21
+ },
22
+ {
23
+ "id": "AC-303-2",
24
+ "description": "The notes list renders the 25 most recent notes.",
25
+ "testable": true,
26
+ "scope": "deployed"
27
+ }
28
+ ],
29
+ "createdAt": "2026-01-01T00:00:00Z",
30
+ "updatedAt": "2026-10-19T00:00:00Z"
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stravica-ai/rcf-schemas",
3
- "version": "0.7.1",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "JSON Schemas for the Requirements Confidence Framework (RCF). Language-neutral. The contract every RCF tool keys to.",
6
6
  "license": "Apache-2.0",
@@ -72,7 +72,27 @@
72
72
  "disposition": { "$ref": "#/$defs/acDisposition" },
73
73
  "vendorCitation": { "$ref": "#/$defs/acVendorCitation" },
74
74
  "probe": { "$ref": "#/$defs/acProbe" },
75
- "provenance": { "$ref": "#/$defs/acProvenance" }
75
+ "provenance": { "$ref": "#/$defs/acProvenance" },
76
+ "supersededBy": {
77
+ "$ref": "https://schemas.stravica.io/rcf/v0.6.0/common.schema.json#/$defs/acId",
78
+ "description": "Id of the acceptance criterion that replaces this one. Presence marks the criterion as retired: it stays in the record as history and consumers stop treating it as live (a coverage audit does not require a bound test for it). Requires supersededOn and supersessionNote. Optional at schema level; that the successor exists, is not this criterion, and forms no cycle are cross-record rules for the consumer."
79
+ },
80
+ "supersededOn": {
81
+ "type": "string",
82
+ "format": "date",
83
+ "description": "ISO-8601 date (YYYY-MM-DD) the criterion was superseded. Required with supersededBy, and valid only alongside it."
84
+ },
85
+ "supersessionNote": {
86
+ "type": "string",
87
+ "minLength": 1,
88
+ "pattern": "\\S",
89
+ "description": "Why the criterion was retired and how its successor covers it (for example the decision id that ruled the change). Required with supersededBy, and valid only alongside it. At least one non-whitespace character is required."
90
+ }
91
+ },
92
+ "dependentRequired": {
93
+ "supersededBy": ["supersededOn", "supersessionNote"],
94
+ "supersededOn": ["supersededBy"],
95
+ "supersessionNote": ["supersededBy"]
76
96
  }
77
97
  },
78
98
  "acOwnerRef": {
@@ -125,7 +145,7 @@
125
145
  "path": {
126
146
  "type": "string",
127
147
  "minLength": 1,
128
- "description": "URL path the probe requests, relative to the run's --url (for example /healthz or /api/notes?limit=1). The only required field."
148
+ "description": "URL path the probe requests, relative to the run's --url (for example /healthz or /api/notes?limit=1). Required. The request target, not an assertion: a probe also needs at least one of expectStatus, expectHeader, expectBody or selector."
129
149
  },
130
150
  "expectStatus": {
131
151
  "type": "integer",
@@ -138,15 +158,22 @@
138
158
  "selector": {
139
159
  "type": "string",
140
160
  "minLength": 1,
141
- "description": "DOM selector that must be present in the rendered response."
161
+ "pattern": "\\S",
162
+ "description": "DOM selector that must be present in the rendered response. At least one non-whitespace character is required."
142
163
  },
143
164
  "parityEnv": {
144
165
  "type": "string",
145
166
  "minLength": 1,
146
- "description": "Target environment the probe is scoped to; the consuming resolver pairs it with rcf verify run --parity-env."
167
+ "description": "Target environment the probe is scoped to; the consuming resolver pairs it with rcf verify run --parity-env. Scopes the probe and is not an assertion."
147
168
  }
148
169
  },
149
- "description": "Deterministic per-AC probe resolved by rcf verify run (rcf-lite issue 349). Absent means the criterion stays critique-only. Shape follows the Probe record in rcf-lite TAC-4150; the schema constrains shape only and the resolution semantics belong to the consumer."
170
+ "anyOf": [
171
+ { "properties": { "expectStatus": true }, "required": ["expectStatus"] },
172
+ { "properties": { "expectHeader": true }, "required": ["expectHeader"] },
173
+ { "properties": { "expectBody": true }, "required": ["expectBody"] },
174
+ { "properties": { "selector": true }, "required": ["selector"] }
175
+ ],
176
+ "description": "Deterministic per-AC probe resolved by rcf verify run (rcf-lite issue 349). Absent means the criterion stays critique-only. Shape follows the Probe record in rcf-lite TAC-4150; the schema constrains shape only and the resolution semantics belong to the consumer. From 0.9.0 a probe carries at least one response assertion (anyOf: expectStatus, expectHeader, expectBody or selector): a probe with only path, method or parityEnv asserts nothing and is rejected. Each anyOf arm declares its property locally so ajv strictRequired accepts it."
150
177
  },
151
178
  "acProbeExpectHeader": {
152
179
  "type": "object",
@@ -156,12 +183,14 @@
156
183
  "name": {
157
184
  "type": "string",
158
185
  "minLength": 1,
159
- "description": "Response header name."
186
+ "pattern": "\\S",
187
+ "description": "Response header name. At least one non-whitespace character is required."
160
188
  },
161
189
  "match": {
162
190
  "type": "string",
163
191
  "minLength": 1,
164
- "description": "Value the named header must match. Match semantics belong to the consuming resolver."
192
+ "pattern": "\\S",
193
+ "description": "Value the named header must match. Match semantics belong to the consuming resolver. At least one non-whitespace character is required."
165
194
  }
166
195
  },
167
196
  "description": "Response header assertion on an acceptance-criterion probe. Both name and match are required."
@@ -173,12 +202,14 @@
173
202
  "contains": {
174
203
  "type": "string",
175
204
  "minLength": 1,
176
- "description": "Literal text the response body must contain."
205
+ "pattern": "\\S",
206
+ "description": "Literal text the response body must contain. At least one non-whitespace character is required."
177
207
  },
178
208
  "matches": {
179
209
  "type": "string",
180
210
  "minLength": 1,
181
- "description": "Regular expression source the response body must match."
211
+ "pattern": "\\S",
212
+ "description": "Regular expression source the response body must match. At least one non-whitespace character is required."
182
213
  }
183
214
  },
184
215
  "minProperties": 1,