rcf-lite 0.7.1 → 0.8.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.
- package/CHANGELOG.md +56 -0
- package/fixtures/canary-manifest.json +6 -6
- package/package.json +5 -3
- package/src/admissibility/enforce.js +142 -0
- package/src/admissibility/index.js +8 -0
- package/src/admissibility/markers.js +104 -0
- package/src/admissibility/scope-lint.js +163 -0
- package/src/cli/create.js +4 -1
- package/src/cli/read.js +7 -1
- package/src/cli/test-suite.js +7 -2
- package/src/core/store/loader.js +4 -1
- package/src/core/store/walker.js +49 -3
- package/src/core/store/writer.js +41 -11
- package/src/finalise/detect.js +51 -29
- package/src/finalise/index.js +16 -2
- package/src/finalise/ingest.js +41 -0
- package/src/mcp/tools.js +10 -2
- package/src/query/index.js +4 -0
- package/src/query/refuse-on-admissibility.js +73 -0
- package/src/ruleset/index.js +140 -0
- package/src/ruleset/ruleset.json +146 -0
- package/src/verify/chain/index.js +31 -0
- package/src/verify/verdict/index.js +67 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schemaComment": "rcf-lite shared standards ruleset v1. Machine-readable contract consumed by build-lite's chain-admissibility lint, verify's per-AC scope check, and (from the umbrella release that adds it) rcf-define-lite's elicitation checklist. camelCase per estate convention. The rulesetVersion is stamped at load time from the umbrella package.json (see src/ruleset/index.js) per NV-BL-SR-02: there is no separate ruleset semver, its version is the rcf-lite umbrella version.",
|
|
3
|
+
"id": "rcf-lite-ruleset",
|
|
4
|
+
"provenance": {
|
|
5
|
+
"requirementsDoc": "projects/rcf-build-lite/docs/2026-08-06_build-lite-nextver-requirements.md",
|
|
6
|
+
"rulingSheet": "projects/rcf-build-lite/docs/2026-08-06_nextver-ruling-sheet.md",
|
|
7
|
+
"designWorkItem": "w-2026-07-28-012",
|
|
8
|
+
"ratifiedItems": [1, 2, 3, 4, 6, 7, 8, 11, 14, 15, 16, 17, 19]
|
|
9
|
+
},
|
|
10
|
+
"admissibilityRules": [
|
|
11
|
+
{
|
|
12
|
+
"id": "NV-BL-ADM-01",
|
|
13
|
+
"title": "Every functional REQ terminates in at least one AC observable as behaviour of the running product",
|
|
14
|
+
"requiresScopeTagVocabulary": true,
|
|
15
|
+
"refuseByDefault": true,
|
|
16
|
+
"overrideChannel": "recordedInChain",
|
|
17
|
+
"notes": "For every REQ classified functional, the chain must contain at least one AC that is observable in the running product, not only in a library or a fixture. AC-shape classifier tags govern which REQ-shapes must terminate in a runtime- or deployed-scope AC."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "NV-BL-ADM-02",
|
|
21
|
+
"title": "AC-shape classifier assigns each AC a scope tag (library / runtime / deployed)",
|
|
22
|
+
"requiresScopeTagVocabulary": true,
|
|
23
|
+
"refuseByDefault": true,
|
|
24
|
+
"overrideChannel": "migrationStateUnclassified",
|
|
25
|
+
"notes": "The scope-tag vocabulary and the AC schema field that carries the tag live in rcf-schemas. See scopeTagVocabulary below."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "NV-BL-ADM-03",
|
|
29
|
+
"title": "Bound TCs must exercise their AC's declared scope",
|
|
30
|
+
"requiresScopeTagVocabulary": true,
|
|
31
|
+
"refuseByDefault": true,
|
|
32
|
+
"overrideChannel": "migrationStateUnclassified",
|
|
33
|
+
"notes": "For each AC, the bound TC's scope must be equal to or wider than the AC's scope tag. Bootstrap window: chains authored before scope tags land carry scope 'unclassified' and are tolerated for one release cycle."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "NV-BL-ADM-04",
|
|
37
|
+
"title": "Source-comment honesty gate",
|
|
38
|
+
"refuseByDefault": true,
|
|
39
|
+
"overrideChannel": "adrExternalBoundaryOnly",
|
|
40
|
+
"markerVocabularyRef": "sourceCommentMarkers",
|
|
41
|
+
"notes": "Source files bound to any FBS at status complete are scanned for admission markers. Any match refuses FBS completion. Per ratified ruling-sheet item 16, the only permitted override channel is an ADR recording the deferral for a genuine external-boundary blocker; the ADR names the marker text, the blocker, and the reason. NV-BL-ADM-05's generic recorded-override channel no longer covers source markers."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "NV-BL-ADM-05",
|
|
45
|
+
"title": "Chain-admissibility gate is refuse-first, override-recorded",
|
|
46
|
+
"refuseByDefault": true,
|
|
47
|
+
"overrideChannel": "recordedInChain",
|
|
48
|
+
"notes": "A chain failing any admissibility check is refused before build starts. Explicit overrides are permitted for non-source-marker checks, recorded in the chain with rule id, reason, and authoring identity, and surfaced in the finalise summary. Source-comment markers (NV-BL-ADM-04) fall outside this generic override channel."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "NV-BL-ADM-06",
|
|
52
|
+
"title": "Build-stage refusal on ruleset-version drift",
|
|
53
|
+
"refuseByDefault": true,
|
|
54
|
+
"overrideChannel": "recordedInChain",
|
|
55
|
+
"notes": "At build start, build-lite compares the ruleset version the chain declares it was authored against with the version the shipping lint enforces. Behaviour-changing drift refuses; additive-only drift warns. Ratified 2026-08-11 ruling-sheet item 7: define-stage drift is a warning-with-acknowledgement; build-stage drift is a refusal."
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"gateRules": [
|
|
59
|
+
{
|
|
60
|
+
"id": "NV-BL-GATE-01",
|
|
61
|
+
"title": "Pull rcf-verify profile-vs-AC check into the REVIEW stage",
|
|
62
|
+
"notes": "The profile-vs-AC scope-mismatch check runs at REVIEW time for every FBS, not only at finalise. Verify carries the check; the review-stage gate consumes the same helper."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "NV-BL-GATE-02",
|
|
66
|
+
"title": "Deploy-smoke FBS is a chain-mandatory element for any REQ with deployed-scope ACs",
|
|
67
|
+
"requiresScopeTagVocabulary": true,
|
|
68
|
+
"notes": "Any chain whose REQ set contains at least one deployed-scope AC must contain at least one FBS whose build sequence boots the built artefact against a real config and asserts operator-visible behaviour."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "NV-BL-GATE-03",
|
|
72
|
+
"title": "Boot-integration TC template family for runtime-scope ACs",
|
|
73
|
+
"templateFamilyRef": "tcTemplateFamily",
|
|
74
|
+
"notes": "Runtime-scope ACs whose bound TC is not a boot-integration TC or a wider-scope TC fail admissibility (via NV-BL-ADM-03) and REVIEW (via NV-BL-GATE-01)."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "NV-BL-GATE-04",
|
|
78
|
+
"title": "Ruling-set consistency light-check at ruling-application time",
|
|
79
|
+
"checkFamilyRef": "rulingConsistencyChecks",
|
|
80
|
+
"notes": "Light mechanical checks only per ratified ruling-sheet item 4: no two rulings name the same external resource with contradictory constraints; no ruling implies a capability the ruled-in service does not have on the ruled-in tier. Probabilistic and suitability work belongs in rcf-define-lite under DL-REQ-VALIDATE-04."
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"scopeTagVocabulary": {
|
|
84
|
+
"sourcePackage": "@stravica-ai/rcf-schemas",
|
|
85
|
+
"schemaRef": "https://schemas.stravica.io/rcf/v0.4.0/common.schema.json#/$defs/scopeTag",
|
|
86
|
+
"values": [
|
|
87
|
+
{ "value": "library", "description": "Observable in an isolated library or fixture." },
|
|
88
|
+
{ "value": "runtime", "description": "Observable in the running product against local integration." },
|
|
89
|
+
{ "value": "deployed", "description": "Observable on a booted artefact against real config and real network." },
|
|
90
|
+
{ "value": "unclassified", "description": "Migration state for chains authored before the scope-tag vocabulary shipped; ruleset-enforcing consumers decide whether unclassified is currently accepted." }
|
|
91
|
+
],
|
|
92
|
+
"unclassifiedMigrationTolerance": {
|
|
93
|
+
"toleratedFor": "one release cycle from the umbrella release that first ships this ruleset",
|
|
94
|
+
"expiresWhen": "rcf-define-lite ships and can take over elicitation"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"sourceCommentMarkers": [
|
|
98
|
+
{ "marker": "TODO", "caseInsensitive": true },
|
|
99
|
+
{ "marker": "FIXME", "caseInsensitive": true },
|
|
100
|
+
{ "marker": "XXX", "caseInsensitive": true },
|
|
101
|
+
{ "marker": "HACK", "caseInsensitive": true },
|
|
102
|
+
{ "marker": "placeholder", "caseInsensitive": true },
|
|
103
|
+
{ "marker": "v1 refinement", "caseInsensitive": true },
|
|
104
|
+
{ "marker": "deferred", "caseInsensitive": true },
|
|
105
|
+
{ "marker": "stub", "caseInsensitive": true }
|
|
106
|
+
],
|
|
107
|
+
"tcTemplateFamily": [
|
|
108
|
+
{
|
|
109
|
+
"id": "TCT-SERVER-BOOT",
|
|
110
|
+
"title": "Server-boot integration TC",
|
|
111
|
+
"surface": "server",
|
|
112
|
+
"notes": "Boots the built server against a real config and asserts an operator-visible observable (probe fires, page returns 200 with expected content, and so on)."
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "TCT-CLI-INVOKE",
|
|
116
|
+
"title": "CLI-invoke integration TC",
|
|
117
|
+
"surface": "cli",
|
|
118
|
+
"notes": "Invokes the packaged CLI as an installed artefact (not via the workspace source) against a real config and asserts an operator-visible observable."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "TCT-CONTAINER-RUN",
|
|
122
|
+
"title": "Container-run integration TC",
|
|
123
|
+
"surface": "container",
|
|
124
|
+
"notes": "Runs the built container against a real config and asserts an operator-visible observable."
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
"rulingConsistencyChecks": [
|
|
128
|
+
{
|
|
129
|
+
"id": "RCC-EXTERNAL-RESOURCE-CONTRADICTION",
|
|
130
|
+
"title": "No two rulings name the same external resource with contradictory constraints",
|
|
131
|
+
"kind": "lightMechanical",
|
|
132
|
+
"notes": "Detects the Resend-collision class: Ruling A names service.tier=free and Ruling B names service.domainCount=N with N > tier-limit(free)."
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"id": "RCC-TIER-CAPABILITY-MISMATCH",
|
|
136
|
+
"title": "No ruling implies a capability the ruled-in service does not have on the ruled-in tier",
|
|
137
|
+
"kind": "lightMechanical",
|
|
138
|
+
"notes": "Detects the ruling-in of a capability against a tier that cannot deliver it. Deep suitability checks live in rcf-define-lite under DL-REQ-VALIDATE-04."
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
"toolScope": {
|
|
142
|
+
"chainAdmissibility": true,
|
|
143
|
+
"traceabilityAndQueryTools": true,
|
|
144
|
+
"notes": "Per ratified ruling-sheet item 1 addendum: the same rule set governs what a traceability or query tool may return as much as it governs what build may consume. A query tool that hides an admissibility failure is the same class of defect as a build that hides one."
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -119,6 +119,29 @@ function fbsIdsFor(fbsItems, acId) {
|
|
|
119
119
|
return out;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* The bound TCs for `acId`, each carrying its own scope tag (if the TC
|
|
124
|
+
* declares one; unspecified surfaces as `undefined`). 0.8.0 slug-train
|
|
125
|
+
* car 4 (NV-BL-GATE-01: pull rcf-verify profile-vs-AC check into the
|
|
126
|
+
* REVIEW stage). Verify uses this to detect SCOPE-MISMATCH per-AC
|
|
127
|
+
* verdicts: a bound TC whose scope is NARROWER than the AC's scope tag
|
|
128
|
+
* cannot legitimately cover that AC (per NV-BL-ADM-03).
|
|
129
|
+
*
|
|
130
|
+
* @param {object[]} testSuites
|
|
131
|
+
* @param {string} acId
|
|
132
|
+
* @returns {Array<{ tsId: string, tcId: string, scope: string|undefined }>}
|
|
133
|
+
*/
|
|
134
|
+
function boundTcsFor(testSuites, acId) {
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const ts of testSuites ?? []) {
|
|
137
|
+
for (const tc of ts.testCases ?? []) {
|
|
138
|
+
if (tc?.acId !== acId) continue;
|
|
139
|
+
out.push({ tsId: ts.id, tcId: tc.id, scope: tc.scope });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
122
145
|
/**
|
|
123
146
|
* Read the acceptance contract from the chain. Returns the flattened list of
|
|
124
147
|
* acceptance criteria (each mapped back to its user story + requirement — the
|
|
@@ -157,6 +180,7 @@ export async function readChain({ repo, chainRef } = {}) {
|
|
|
157
180
|
|
|
158
181
|
const resolvedRef = chainRef ?? tree.prd?.prdId ?? 'PRD-UNKNOWN';
|
|
159
182
|
const fbsItems = tree.fbsItems ?? [];
|
|
183
|
+
const testSuites = tree.testSuites ?? [];
|
|
160
184
|
const acs = [];
|
|
161
185
|
for (const us of tree.userStories ?? []) {
|
|
162
186
|
for (const ac of us.acceptanceCriteria ?? []) {
|
|
@@ -175,6 +199,13 @@ export async function readChain({ repo, chainRef } = {}) {
|
|
|
175
199
|
serviceAttestations: serviceAttestationsFor(fbsItems, ac.id),
|
|
176
200
|
fbsUiBearing: fbsUiBearingFor(fbsItems, ac.id),
|
|
177
201
|
fbsIds: fbsIdsFor(fbsItems, ac.id),
|
|
202
|
+
// 0.8.0 slug-train car 4 (NV-BL-GATE-01, NV-BL-ADM-03): scope
|
|
203
|
+
// tags read straight off rcf-schemas 0.4.3's AC/TC subschemas;
|
|
204
|
+
// the verdict layer runs the scope check per-AC in REVIEW,
|
|
205
|
+
// matching what NV-BL-GATE-01 pulls in from the finalise-time
|
|
206
|
+
// profile check.
|
|
207
|
+
scope: ac.scope,
|
|
208
|
+
boundTcs: boundTcsFor(testSuites, ac.id),
|
|
178
209
|
});
|
|
179
210
|
}
|
|
180
211
|
}
|
|
@@ -50,12 +50,19 @@ export const VERDICTS = Object.freeze([...FINDING_SEVERITIES, 'NOT-DEPLOYED', 'B
|
|
|
50
50
|
* `report.perAcVerdicts[]`. Consumed by `rcf finalise` to refuse promotion
|
|
51
51
|
* to `verified` on any of these AC-level verdicts (see
|
|
52
52
|
* `packages/rcf-lite/src/finalise/ingest.js:findMockOnlyDeclaredAcs`).
|
|
53
|
+
*
|
|
54
|
+
* 0.8.0 slug-train car 4 addition (NV-BL-GATE-01 + NV-BL-ADM-03):
|
|
55
|
+
* - SCOPE-MISMATCH: an AC whose declared scope tag exceeds the scope
|
|
56
|
+
* of every bound TC. Verify emits this at REVIEW time so the pull-in
|
|
57
|
+
* from the finalise-time profile-vs-AC check fires per FBS rather
|
|
58
|
+
* than only at finalise.
|
|
53
59
|
*/
|
|
54
60
|
export const PER_AC_VERDICTS = Object.freeze([
|
|
55
61
|
'MOCK-ONLY-DECLARED',
|
|
56
62
|
'BLOCKED-BY-DECLARATION',
|
|
57
63
|
'UI-BASELINE-UNMET',
|
|
58
64
|
'BROWSER-VERIFICATION-MISSING',
|
|
65
|
+
'SCOPE-MISMATCH',
|
|
59
66
|
]);
|
|
60
67
|
|
|
61
68
|
/**
|
|
@@ -220,6 +227,61 @@ export function uiPerAcVerdict(ac, browserVerification = []) {
|
|
|
220
227
|
return null;
|
|
221
228
|
}
|
|
222
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Rank each scope tag so we can compare "TC scope >= AC scope"
|
|
232
|
+
* numerically. Mirrors `packages/rcf-lite/src/admissibility/scope-lint.js`
|
|
233
|
+
* (single source of truth on rcf-schemas 0.4.3's scopeTag values;
|
|
234
|
+
* this copy stays deliberately local so the verdict layer does not
|
|
235
|
+
* depend on the admissibility module -- both consume the same schemas
|
|
236
|
+
* vocabulary and the same values, and duplicating the ranking here
|
|
237
|
+
* keeps the verdict layer standalone).
|
|
238
|
+
*/
|
|
239
|
+
const SCOPE_RANK = Object.freeze({
|
|
240
|
+
library: 1,
|
|
241
|
+
runtime: 2,
|
|
242
|
+
deployed: 3,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 0.8.0 slug-train car 4 (NV-BL-GATE-01 + NV-BL-ADM-03): the profile-vs-AC
|
|
247
|
+
* scope-mismatch check, pulled from finalise into REVIEW so it fires
|
|
248
|
+
* per FBS. An AC declared runtime-scope bound only to library-scope TCs
|
|
249
|
+
* emits SCOPE-MISMATCH; a wider TC (deployed covering runtime) is fine;
|
|
250
|
+
* an AC with no scope declaration is silent here (NV-BL-ADM-02 catches
|
|
251
|
+
* it at admissibility); a bound TC with no declared scope is silent
|
|
252
|
+
* here (bootstrap window; NV-BL-ADM-03 in the admissibility lint
|
|
253
|
+
* catches it there).
|
|
254
|
+
*
|
|
255
|
+
* @param {object} ac - flattened AC with `scope` and `boundTcs`
|
|
256
|
+
* @returns {{ verdict: 'SCOPE-MISMATCH', reason: string } | null}
|
|
257
|
+
*/
|
|
258
|
+
export function scopePerAcVerdict(ac) {
|
|
259
|
+
if (!ac || typeof ac.scope !== 'string') return null;
|
|
260
|
+
if (!Object.prototype.hasOwnProperty.call(SCOPE_RANK, ac.scope)) return null;
|
|
261
|
+
const boundTcs = Array.isArray(ac.boundTcs) ? ac.boundTcs : [];
|
|
262
|
+
if (boundTcs.length === 0) return null; // coverage rule handles "no TC at all".
|
|
263
|
+
const acRank = SCOPE_RANK[ac.scope];
|
|
264
|
+
const narrower = [];
|
|
265
|
+
let anyAtLeastAsWide = false;
|
|
266
|
+
for (const tc of boundTcs) {
|
|
267
|
+
if (typeof tc?.scope !== 'string') continue;
|
|
268
|
+
if (!Object.prototype.hasOwnProperty.call(SCOPE_RANK, tc.scope)) continue;
|
|
269
|
+
const tcRank = SCOPE_RANK[tc.scope];
|
|
270
|
+
if (tcRank >= acRank) {
|
|
271
|
+
anyAtLeastAsWide = true;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
narrower.push({ tsId: tc.tsId, tcId: tc.tcId, scope: tc.scope });
|
|
275
|
+
}
|
|
276
|
+
if (anyAtLeastAsWide) return null;
|
|
277
|
+
if (narrower.length === 0) return null;
|
|
278
|
+
const detail = narrower.map((n) => `${n.tcId} on ${n.tsId} (scope=${n.scope})`).join(', ');
|
|
279
|
+
return {
|
|
280
|
+
verdict: 'SCOPE-MISMATCH',
|
|
281
|
+
reason: `AC ${ac.acId} is scope=${ac.scope} but every bound TC is narrower: ${detail}. NV-BL-GATE-01 pulls this check into REVIEW; NV-BL-ADM-03 refuses at admissibility.`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
223
285
|
/**
|
|
224
286
|
* Emit per-AC verdicts across the whole chain. Combines Track A's service
|
|
225
287
|
* attestation verdicts with Track B's UI-baseline verdicts. An AC can carry
|
|
@@ -229,6 +291,9 @@ export function uiPerAcVerdict(ac, browserVerification = []) {
|
|
|
229
291
|
* `packages/rcf-lite/src/finalise/ingest.js:findMockOnlyDeclaredAcs`, which
|
|
230
292
|
* filters on verdict class and does not deduplicate by acId.
|
|
231
293
|
*
|
|
294
|
+
* 0.8.0 slug-train car 4: SCOPE-MISMATCH runs here too, alongside the
|
|
295
|
+
* attestation and UI verdicts.
|
|
296
|
+
*
|
|
232
297
|
* @param {object} opts
|
|
233
298
|
* @param {Array<object>} opts.acs - flattened ACs from `readChain`
|
|
234
299
|
* @param {object[]} [opts.browserVerification] - manifest.browserVerification[]
|
|
@@ -241,6 +306,8 @@ export function derivePerAcVerdicts({ acs = [], browserVerification = [] } = {})
|
|
|
241
306
|
if (attest) out.push({ acId: ac.acId, verdict: attest.verdict, reason: attest.reason });
|
|
242
307
|
const ui = uiPerAcVerdict(ac, browserVerification);
|
|
243
308
|
if (ui) out.push({ acId: ac.acId, verdict: ui.verdict, reason: ui.reason });
|
|
309
|
+
const scopeMismatch = scopePerAcVerdict(ac);
|
|
310
|
+
if (scopeMismatch) out.push({ acId: ac.acId, verdict: scopeMismatch.verdict, reason: scopeMismatch.reason });
|
|
244
311
|
}
|
|
245
312
|
return out;
|
|
246
313
|
}
|