rcf-lite 0.17.0 → 0.18.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 +22 -0
- package/blueprints/application-api-rest/README.md +5 -1
- package/blueprints/application-api-rest/blueprint.json +1 -1
- package/blueprints/application-api-rest/contributions/requirements/application-api-rest-req-006.json +4 -4
- package/blueprints/application-api-rest/contributions/tacs/tac-306-application-api-rest-operability.json +9 -8
- package/blueprints/application-api-rest/contributions/user-stories/application-api-rest-us-2103.json +3 -3
- package/blueprints/application-api-rest/contributions/user-stories/application-api-rest-us-2108.json +27 -27
- package/blueprints/application-api-rest/docs/topics.md +2 -2
- package/blueprints/application-api-rest/guide/application-api-rest.md +5 -1
- package/blueprints/application-spa/docs/topics.md +2 -2
- package/blueprints/delivery-ci-workflows/docs/topics.md +2 -2
- package/blueprints/deploy-cloudflare-workers/docs/topics.md +2 -2
- package/blueprints/email-smtp-resend/docs/topics.md +2 -2
- package/blueprints/observability-essentials/README.md +6 -2
- package/blueprints/observability-essentials/blueprint.json +133 -33
- package/blueprints/observability-essentials/contributions/adrs/adr-801-observability-essentials-health-probes.json +4 -4
- package/blueprints/observability-essentials/contributions/adrs/adr-802-observability-essentials-readiness-semantics.json +4 -4
- package/blueprints/observability-essentials/contributions/requirements/observability-essentials-req-001.json +4 -4
- package/blueprints/observability-essentials/contributions/requirements/observability-essentials-req-002.json +4 -4
- package/blueprints/observability-essentials/contributions/tacs/tac-801-observability-essentials-liveness-probe.json +10 -9
- package/blueprints/observability-essentials/contributions/tacs/tac-802-observability-essentials-readiness-probe.json +17 -11
- package/blueprints/observability-essentials/contributions/user-stories/observability-essentials-us-7101.json +12 -3
- package/blueprints/observability-essentials/contributions/user-stories/observability-essentials-us-7102.json +12 -3
- package/blueprints/observability-essentials/docs/topics.md +15 -8
- package/blueprints/observability-essentials/guide/observability-essentials.md +9 -3
- package/blueprints/observability-probe-endpoints/README.md +5 -1
- package/blueprints/observability-probe-endpoints/blueprint.json +116 -24
- package/blueprints/observability-probe-endpoints/contributions/adrs/adr-1503-observability-probe-endpoints-kubernetes-default.json +5 -5
- package/blueprints/observability-probe-endpoints/contributions/user-stories/observability-probe-endpoints-us-14102.json +11 -2
- package/blueprints/observability-probe-endpoints/contributions/user-stories/observability-probe-endpoints-us-14107.json +11 -2
- package/blueprints/observability-probe-endpoints/docs/topics.md +6 -6
- package/blueprints/observability-probe-endpoints/guide/observability-probe-endpoints.md +10 -0
- package/blueprints/persistence-data-d1/docs/topics.md +2 -2
- package/blueprints/persistence-data-sqlite/docs/topics.md +2 -2
- package/blueprints/security-auth-clerk/docs/topics.md +2 -2
- package/blueprints/security-auth-keycloak/docs/topics.md +2 -2
- package/blueprints/security-auth-magic-link/docs/topics.md +2 -2
- package/blueprints/security-auth-oauth2/docs/topics.md +2 -2
- package/blueprints/security-secrets-management/docs/topics.md +2 -2
- package/fixtures/canary-manifest.json +6 -6
- package/package.json +1 -1
- package/rcf/code-nodes/cn-074.json +19 -0
- package/rcf/code-nodes/cn-075.json +15 -0
- package/rcf/code-nodes/cn-076.json +14 -0
- package/rcf/fbs/fbs-024.json +24 -0
- package/rcf/fbs/fbs-025.json +25 -0
- package/rcf/fbs/fbs-026.json +27 -0
- package/rcf/fbs/fbs-027.json +25 -0
- package/rcf/requirements/req-012.json +22 -0
- package/rcf/test-suites/ts-034.json +32 -0
- package/rcf/test-suites/ts-035.json +23 -0
- package/rcf/test-suites/ts-036.json +65 -0
- package/rcf/test-suites/ts-037.json +55 -0
- package/rcf/user-stories/us-1201.json +34 -0
- package/rcf/user-stories/us-1202.json +25 -0
- package/rcf/user-stories/us-1203.json +43 -0
- package/rcf/user-stories/us-1204.json +25 -0
- package/releases/releases.yaml +11 -1
- package/src/blueprint/index.js +1 -0
- package/src/blueprint/remove-resolution.js +104 -0
- package/src/cli/blueprint.js +41 -0
- package/src/cli/doctor.js +76 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "TS-036",
|
|
3
|
+
"usId": "US-1203",
|
|
4
|
+
"title": "Each amended blueprint carries the ratified alignment facts (probe-endpoints, essentials, api-rest)",
|
|
5
|
+
"purpose": "Cover the three ACs on US-1203: probe-endpoints has the optional startup ACs and composing section; essentials has path-neutral REQs/TACs and boot-refusal ACs; api-rest defers all probe paths to the resolved set.",
|
|
6
|
+
"testLevel": "integration",
|
|
7
|
+
"acIds": [
|
|
8
|
+
"AC-1203-1",
|
|
9
|
+
"AC-1203-2",
|
|
10
|
+
"AC-1203-3"
|
|
11
|
+
],
|
|
12
|
+
"testCases": [
|
|
13
|
+
{
|
|
14
|
+
"id": "TC-036-probe-endpoints-optional-startup",
|
|
15
|
+
"acId": "AC-1203-1",
|
|
16
|
+
"description": "probe-endpoints v1.1.0 declares the optional startup path across ADR-1503, US-14102 AC-14102-4, US-14107 AC-14107-4, and the guide composing section",
|
|
17
|
+
"testPointer": "test/blueprint/probe-path-alignment.test.js::assertion 4: bare compose default declares /live and /ready; optional /startup; loadBalancer /health",
|
|
18
|
+
"status": "pending",
|
|
19
|
+
"scope": "library"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "TC-036-essentials-path-neutral-and-refusal",
|
|
23
|
+
"acId": "AC-1203-2",
|
|
24
|
+
"description": "essentials v2.0.0 restates REQ-001/002 path-neutral, marks TAC-801/802 path as REQUIRED with no default, and adds AC-7101-4/AC-7102-4 for the PROBE_INTERFACE_PATHS_MISSING boot refusal",
|
|
25
|
+
"testPointer": "test/blueprint/probe-path-alignment.test.js::assertion 5: essentials-alone declares required probeInterface.paths and refuses boot when absent",
|
|
26
|
+
"status": "pending",
|
|
27
|
+
"scope": "library"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "TC-036-essentials-no-shipped-default",
|
|
31
|
+
"acId": "AC-1203-2",
|
|
32
|
+
"description": "TAC-801/802 interfaces carry no literal /healthz or /readyz path defaults and mark the path parameter as REQUIRED",
|
|
33
|
+
"testPointer": "test/blueprint/probe-path-alignment.test.js::assertion 7: essentials-alone shape gates on required probeInterface.paths, TACs carry no shipped default",
|
|
34
|
+
"status": "pending",
|
|
35
|
+
"scope": "library"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "TC-036-essentials-no-dual-handler",
|
|
39
|
+
"acId": "AC-1203-2",
|
|
40
|
+
"description": "essentials TAC-801/802 responsibilities name the no-dual-handler semantics when probe-endpoints is composed",
|
|
41
|
+
"testPointer": "test/blueprint/probe-path-alignment.test.js::assertion 9: essentials TAC-801/802 promise no dual handler registration when probe-endpoints is composed",
|
|
42
|
+
"status": "pending",
|
|
43
|
+
"scope": "library"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "TC-036-api-rest-auth-exempt-list",
|
|
47
|
+
"acId": "AC-1203-3",
|
|
48
|
+
"description": "api-rest US-2108 AC-2108-5 binds the auth-exempt assertion to the resolved probe path set naming getExemptPathSet, and TAC-306 responsibilities carry the same binding",
|
|
49
|
+
"testPointer": "test/blueprint/probe-path-alignment.test.js::assertion 6: api-rest auth-exempt list equals the probe-endpoints resolved path set",
|
|
50
|
+
"status": "pending",
|
|
51
|
+
"scope": "library"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "TC-036-api-rest-startup-consumption",
|
|
55
|
+
"acId": "AC-1203-3",
|
|
56
|
+
"description": "api-rest US-2108 AC-2108-4 binds the startup semantics to the resolved startup path and names the startup.enabled flag or probeInterface.paths.startup as the enablement source",
|
|
57
|
+
"testPointer": "test/blueprint/probe-path-alignment.test.js::assertion 8: startup binding when enabled is declared on probe-endpoints and consumed by api-rest",
|
|
58
|
+
"status": "pending",
|
|
59
|
+
"scope": "library"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"status": "draft",
|
|
63
|
+
"createdAt": "2026-09-04T00:00:00Z",
|
|
64
|
+
"updatedAt": "2026-09-04T00:00:00Z"
|
|
65
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "TS-037",
|
|
3
|
+
"usId": "US-1204",
|
|
4
|
+
"title": "`rcf define blueprint remove-resolution <adr-id>` verb: remove + idempotent + refuse behaviours",
|
|
5
|
+
"purpose": "Cover the single AC on US-1204: the ratified three-behaviour surface (remove entry, idempotent re-run, refuse-exit-2 on malformed or unknown id) end-to-end through the CLI, plus the help block advertising the verb.",
|
|
6
|
+
"testLevel": "integration",
|
|
7
|
+
"acIds": [
|
|
8
|
+
"AC-1204-1"
|
|
9
|
+
],
|
|
10
|
+
"testCases": [
|
|
11
|
+
{
|
|
12
|
+
"id": "TC-037-remove-drops-entry-and-prints-line",
|
|
13
|
+
"acId": "AC-1204-1",
|
|
14
|
+
"description": "One resolutions[] entry seeded with resolvedByAdrId 'ADR-011-health-probes'; the verb prints the removal line naming the id, topic 'healthProbes' and resolution id, exits 0, drops the single entry from manifest.resolutions[], and leaves every other manifest section and the ruling ADR file on disk unchanged.",
|
|
15
|
+
"testPointer": "test/blueprint/cli.test.js::rcf blueprint remove-resolution drops the resolutions[] entry and prints one result line (spec A2)",
|
|
16
|
+
"status": "pending",
|
|
17
|
+
"scope": "library"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "TC-037-idempotent-second-run",
|
|
21
|
+
"acId": "AC-1204-1",
|
|
22
|
+
"description": "After a successful remove, a second run of the same command exits 0 and prints the 'nothing to remove' line without writing the manifest.",
|
|
23
|
+
"testPointer": "test/blueprint/cli.test.js::rcf blueprint remove-resolution is idempotent on re-run when the ruling ADR still exists on the tree",
|
|
24
|
+
"status": "pending",
|
|
25
|
+
"scope": "library"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "TC-037-refuse-malformed-adr-id",
|
|
29
|
+
"acId": "AC-1204-1",
|
|
30
|
+
"description": "A malformed id 'not-an-adr' exits 2 with the well-formed-ADR-id error on stderr.",
|
|
31
|
+
"testPointer": "test/blueprint/cli.test.js::rcf blueprint remove-resolution refuses exit 2 on a malformed <adr-id>",
|
|
32
|
+
"status": "pending",
|
|
33
|
+
"scope": "library"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "TC-037-refuse-unknown-adr-id",
|
|
37
|
+
"acId": "AC-1204-1",
|
|
38
|
+
"description": "A well-formed id ('ADR-999-nowhere') that names no ADR on the tree and no resolutions[] entry exits 2 with the 'is not a resolution entry' error on stderr.",
|
|
39
|
+
"testPointer": "test/blueprint/cli.test.js::rcf blueprint remove-resolution refuses exit 2 when <adr-id> is well-formed but names no ADR anywhere on the tree",
|
|
40
|
+
"status": "pending",
|
|
41
|
+
"scope": "library"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "TC-037-help-lists-verb",
|
|
45
|
+
"acId": "AC-1204-1",
|
|
46
|
+
"description": "`rcf help define blueprint` output contains the `remove-resolution <adr-id>` verb block including the idempotent-on-re-run line.",
|
|
47
|
+
"testPointer": "test/blueprint/cli.test.js::rcf help define blueprint prints the remove-resolution verb",
|
|
48
|
+
"status": "pending",
|
|
49
|
+
"scope": "library"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"status": "draft",
|
|
53
|
+
"createdAt": "2026-09-04T00:00:00Z",
|
|
54
|
+
"updatedAt": "2026-09-04T00:00:00Z"
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "US-1201",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "REQ-012",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"status": "draft",
|
|
7
|
+
"title": "observability-essentials + observability-probe-endpoints + application-api-rest compose cleanly in every ordering with no globalAdrTopic conflict on healthProbes or readinessSemantics",
|
|
8
|
+
"asA": "operator applying the three post-alignment observability + api-rest blueprints on one project in any order",
|
|
9
|
+
"iWant": "the three amended blueprints (essentials v2.0.0, probe-endpoints v1.1.0, api-rest v2.0.0) to apply cleanly in every ordering, with observability-probe-endpoints as the sole scope:global claimant of healthProbes and readinessSemantics in the resulting manifest",
|
|
10
|
+
"soThat": "a project that reaches for all three shipped blueprints inherits a single-owner probe-path surface without an operator-side supersede ADR or a manifest-editing follow-up",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-1201-1",
|
|
14
|
+
"description": "Applying the three blueprints in every ordering (six permutations) into a freshly initialised project tree succeeds with no `globalAdrTopic` conflict on `healthProbes` or `readinessSemantics`; the resulting `rcf/manifest.json` carries all three blueprint records.",
|
|
15
|
+
"given": "a freshly initialised rcf-lite project tree and the three shipped blueprints at essentials v2.0.0, probe-endpoints v1.1.0, api-rest v2.0.0",
|
|
16
|
+
"when": "for each of the six permutations of the three blueprints, applyBlueprint runs three times in that order against a fresh project tree",
|
|
17
|
+
"then": "no ordering returns a `globalAdrTopic` conflict on healthProbes or readinessSemantics, and every ordering ends with manifest.blueprints containing observability-essentials, observability-probe-endpoints, and application-api-rest",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "library"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "AC-1201-2",
|
|
23
|
+
"description": "In the applied manifest, exactly one blueprint declares `scope: global` with `topic: healthProbes` (observability-probe-endpoints ADR-1501) and exactly one declares `scope: global` with `topic: readinessSemantics` (observability-probe-endpoints ADR-1502); zero from essentials, zero from api-rest.",
|
|
24
|
+
"given": "a project with all three amended blueprints applied",
|
|
25
|
+
"when": "manifest.blueprints[].contributions[] is walked for scope:global ADRs on the two topics",
|
|
26
|
+
"then": "the claimant list for healthProbes equals [\"observability-probe-endpoints\"] and the claimant list for readinessSemantics equals [\"observability-probe-endpoints\"]",
|
|
27
|
+
"testable": true,
|
|
28
|
+
"scope": "library"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"tacIds": [],
|
|
32
|
+
"createdAt": "2026-09-04T00:00:00Z",
|
|
33
|
+
"updatedAt": "2026-09-04T00:00:00Z"
|
|
34
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "US-1202",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "REQ-012",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"status": "draft",
|
|
7
|
+
"title": "The applied tree carries no literal probe path (/healthz, /readyz, /healthz/*) outside observability-probe-endpoints prose",
|
|
8
|
+
"asA": "operator or reviewer grepping an applied project tree for literal probe paths after re-applying the three amended blueprints",
|
|
9
|
+
"iWant": "zero matches for /healthz, /readyz, /healthz/live, /healthz/ready or /healthz/startup in the applied `rcf/` tree, so the shelf ships one probe-path opinion (the resolved one) and no rogue literals",
|
|
10
|
+
"soThat": "a project that composes the three shipped blueprints never inherits a stale literal path binding that disagrees with the resolved probe path set",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-1202-1",
|
|
14
|
+
"description": "Grepping the applied `rcf/` tree of a project that has re-applied observability-essentials v2.0.0 + observability-probe-endpoints v1.1.0 + application-api-rest v2.0.0 for the strings `/healthz/live`, `/healthz/ready`, `/healthz/startup`, `/healthz`, and `/readyz` returns zero matches across all contribution JSON files.",
|
|
15
|
+
"given": "a project with all three amended blueprints applied",
|
|
16
|
+
"when": "the applied `rcf/` tree is grepped for the five literal probe-path strings above",
|
|
17
|
+
"then": "the match count is zero (matches remain only in the source blueprint READMEs and guides for observability-probe-endpoints, never in the applied contribution JSON)",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "library"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": [],
|
|
23
|
+
"createdAt": "2026-09-04T00:00:00Z",
|
|
24
|
+
"updatedAt": "2026-09-04T00:00:00Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "US-1203",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "REQ-012",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"status": "draft",
|
|
7
|
+
"title": "Each amended blueprint carries the ratified alignment facts: probe-endpoints optional startup + composing section; essentials path-neutral REQs/TACs and PROBE_INTERFACE_PATHS_MISSING; api-rest defers all probe paths",
|
|
8
|
+
"asA": "reviewer walking the three amended blueprint source directories after the alignment lands",
|
|
9
|
+
"iWant": "each blueprint to carry the shape the spec ratified: observability-probe-endpoints v1.1.0 adds the optional startup path and a composing section; observability-essentials v2.0.0 restates REQ-001/002 and TAC-801/802 path-neutral and names the PROBE_INTERFACE_PATHS_MISSING boot refusal; application-api-rest v2.0.0 defers probe paths to the resolved set with a matching AC on the auth-exempt list and a startup AC that consumes the optional path",
|
|
10
|
+
"soThat": "a downstream project (or a gate reviewer walking the shelf) reads the ratified alignment off the source blueprints directly, without needing to reconstruct the intent from the spec",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-1203-1",
|
|
14
|
+
"description": "observability-probe-endpoints/blueprint.json declares version 1.1.0. US-14102 carries a new AC-14102-4 naming the three-path resolution (/live, /ready, /startup) and the startup handler when `probeInterface.options.kubernetes.startup.enabled: true`. US-14107 carries a new AC-14107-4 naming the three-entry auth-exempt list when startup is enabled. ADR-1503 decision text names the startup.enabled flag, the same body shape, and the fused topology by default. `guide/observability-probe-endpoints.md` carries a \"Composing with observability-essentials and application-api-rest\" section naming the deferral pattern and the compose test at `packages/rcf-lite/test/blueprint/probe-path-alignment.test.js` as the ratified truth.",
|
|
15
|
+
"given": "the shipped observability-probe-endpoints blueprint source directory at HEAD",
|
|
16
|
+
"when": "blueprint.json, US-14102, US-14107, ADR-1503, and the guide are read",
|
|
17
|
+
"then": "blueprint.json version equals 1.1.0; AC-14102-4 and AC-14107-4 exist and name the startup path; ADR-1503 decision names startup.enabled; the guide carries the composing section naming the compose test path",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "library"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "AC-1203-2",
|
|
23
|
+
"description": "observability-essentials/blueprint.json declares version 2.0.0; the ADR-801 and ADR-802 contribution entries no longer carry `scope: \"global\"` or `topic`. REQ-001 and REQ-002 restate path-neutral and name `probeInterface.paths.liveness` / `probeInterface.paths.readiness` as the required configuration key. TAC-801 and TAC-802 mark the path as REQUIRED with no shipped default and name the `PROBE_INTERFACE_PATHS_MISSING` stable-coded boot refusal on the responsibilities list. US-7101 gains AC-7101-4 and US-7102 gains AC-7102-4 covering the boot-refusal-on-missing-config semantics. `docs/topics.md` shrinks the global-topics table to `statusPageContract` alone and records `healthProbes` and `readinessSemantics` in a \"Historical global topics (dropped in v2.0.0)\" section.",
|
|
24
|
+
"given": "the shipped observability-essentials blueprint source directory at HEAD",
|
|
25
|
+
"when": "blueprint.json, ADR-801, ADR-802, REQ-001, REQ-002, TAC-801, TAC-802, US-7101, US-7102, and docs/topics.md are read",
|
|
26
|
+
"then": "blueprint.json version equals 2.0.0; ADR-801/802 contribution entries carry no scope or topic field; REQ-001/002 name probeInterface.paths and refuses-boot; TAC-801/802 mark path as REQUIRED and name PROBE_INTERFACE_PATHS_MISSING; AC-7101-4 and AC-7102-4 exist; topics.md carries the Historical global topics section",
|
|
27
|
+
"testable": true,
|
|
28
|
+
"scope": "library"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "AC-1203-3",
|
|
32
|
+
"description": "application-api-rest/blueprint.json declares version 2.0.0. REQ-006 restates path-neutral naming observability-probe-endpoints as the probe-path owner and probeInterface.paths as the essentials-alone fallback. US-2108 all eight ACs bind to \"the resolved liveness path\", \"the resolved readiness path\", or \"the resolved startup path\"; AC-2108-5 binds the auth-exempt assertion to the resolved probe path set (naming `getExemptPathSet` from TAC-1501). TAC-306 responsibilities restate the three probe surfaces at their resolved paths, drop the literal path strings, and name the auth-exempt consumption of the resolved set. US-2103 versioning-exemption AC no longer carries the literal `/healthz/*` and names the resolved probe paths instead.",
|
|
33
|
+
"given": "the shipped application-api-rest blueprint source directory at HEAD",
|
|
34
|
+
"when": "blueprint.json, REQ-006, US-2108 (all eight ACs), TAC-306, and US-2103 are read",
|
|
35
|
+
"then": "blueprint.json version equals 2.0.0; REQ-006 names probe-endpoints and probeInterface.paths as the two path-resolution sources; every US-2108 AC contains \"resolved\" and none contains a literal /healthz/* prefix; TAC-306 responsibilities name the resolved probe path set and getExemptPathSet or probeInterface.paths; US-2103 no longer contains /healthz/*",
|
|
36
|
+
"testable": true,
|
|
37
|
+
"scope": "library"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"tacIds": [],
|
|
41
|
+
"createdAt": "2026-09-04T00:00:00Z",
|
|
42
|
+
"updatedAt": "2026-09-04T00:00:00Z"
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "US-1204",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "REQ-012",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"status": "draft",
|
|
7
|
+
"title": "Operator running `rcf define blueprint remove-resolution <adr-id>` drops a redundant historical resolutions[] entry cleanly (spec section 9 remedy)",
|
|
8
|
+
"asA": "operator running `rcf doctor --check probe-path-owner` on a project that upgraded to observability-essentials v2.0.0 + observability-probe-endpoints v1.1.0 and now sees a redundant-<topic>-resolution drift line",
|
|
9
|
+
"iWant": "the `rcf define blueprint remove-resolution <adr-id>` verb the drift line and section 9 name to actually exist, drop the named manifest.resolutions[] entry (and nothing else), refuse exit 2 when the id is not a resolution entry, and be idempotent on a second run",
|
|
10
|
+
"soThat": "the section 9 migration remedy printed by the doctor is executable verbatim, and re-running the same command after a successful pass is a no-op rather than a refusal",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-1204-1",
|
|
14
|
+
"description": "The `rcf define blueprint remove-resolution <adr-id>` verb exists on the blueprint CLI (advertised in `rcf help define blueprint`), matches the `<adr-id>` argument against `manifest.resolutions[].resolvedByAdrId`, and exhibits three ratified behaviours: (a) when an entry matches, drop that entry and only that entry from `manifest.resolutions[]` (every other manifest section byte-identical, and the project-level ADR file at `rcf/adrs/<adr-id>.json` untouched), print one result line naming the id, the topic, and the dropped resolution id, and exit 0; (b) when the `<adr-id>` is a well-formed ADR id that still names an ADR present on the project tree but is no longer on any `resolutions[]` entry, print `[blueprint] nothing to remove: '<adr-id>' is not on manifest.resolutions[]` and exit 0 (idempotent on re-run of the same operation); (c) when the `<adr-id>` is malformed against the ADR id grammar, or is well-formed but names no ADR on the tree and no resolutions[] entry, print `[error] blueprint remove-resolution: ...` on stderr and exit 2.",
|
|
15
|
+
"given": "a project whose manifest.resolutions[] carries one globalAdrTopic entry on healthProbes with resolvedByAdrId = 'ADR-011-health-probes' and the ADR file present at rcf/adrs/adr-011-health-probes.json",
|
|
16
|
+
"when": "the operator runs `rcf define blueprint remove-resolution ADR-011-health-probes` once, then a second time, then with 'not-an-adr' and with 'ADR-999-nowhere'",
|
|
17
|
+
"then": "the first run exits 0 with the removal line and drops the single resolutions[] entry (project-ADR file untouched, every other manifest section byte-identical); the second run exits 0 with the 'nothing to remove' line and does not write; 'not-an-adr' exits 2 with 'is not a well-formed ADR id'; 'ADR-999-nowhere' exits 2 with 'is not a resolution entry on this manifest'; and `rcf help define blueprint` output contains the `remove-resolution <adr-id>` verb block",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "library"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": [],
|
|
23
|
+
"createdAt": "2026-09-04T00:00:00Z",
|
|
24
|
+
"updatedAt": "2026-09-04T00:00:00Z"
|
|
25
|
+
}
|
package/releases/releases.yaml
CHANGED
|
@@ -40,8 +40,18 @@
|
|
|
40
40
|
# `npm install rcf-lite`.
|
|
41
41
|
|
|
42
42
|
feedVersion: 1
|
|
43
|
-
latest: "0.
|
|
43
|
+
latest: "0.18.0"
|
|
44
44
|
releases:
|
|
45
|
+
- version: "0.18.0"
|
|
46
|
+
date: "2026-09-04"
|
|
47
|
+
breaking: true
|
|
48
|
+
headlines:
|
|
49
|
+
- "The rcf-lite blueprint shelf now names a single owner for probe paths across the observability, API and Kubernetes surfaces, so a project that composes more than one of these blueprints no longer has to reconcile duplicate claims by hand."
|
|
50
|
+
- "observability-essentials v2.0.0 and application-api-rest v2.0.0 drop their shipped literal /healthz and /readyz path defaults and defer to the resolved path set from observability-probe-endpoints v1.1.0, which also adds an optional Kubernetes /startup path (off by default, on per project via probeInterface.options.kubernetes.startup.enabled)."
|
|
51
|
+
- "rcf doctor grows a probe-path-owner check that flags a project applying more than one blueprint on the probe-path topic; rcf define blueprint remove-resolution <adr-id> is the paired remedy verb that drops a single manifest resolutions entry. Migration: a project on essentials v1.x alone needs one probeInterface.paths line per environment on re-apply unless it also composes probe-endpoints v1.1.0 or later; a project that applied both essentials v1.x and probe-endpoints v1.0.0 with a superseding project-level ADR will see the new doctor check flag those resolutions as redundant with the remove-resolution verb as the hint."
|
|
52
|
+
minAgentAction: null
|
|
53
|
+
notesUrl: "https://stravica.ai/docs/rcf/changelog/"
|
|
54
|
+
|
|
45
55
|
- version: "0.17.0"
|
|
46
56
|
date: "2026-09-04"
|
|
47
57
|
breaking: false
|
package/src/blueprint/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
export { applyBlueprint } from './apply.js';
|
|
4
4
|
export { listBlueprints, enrichRowsWithCategories, groupRowsByCategory } from './list.js';
|
|
5
5
|
export { removeBlueprint } from './remove.js';
|
|
6
|
+
export { removeResolution } from './remove-resolution.js';
|
|
6
7
|
export { loadBlueprint } from './loader.js';
|
|
7
8
|
export { detectGlobalAdrConflicts, detectCrossBlueprintClaims, renderConflictReport, conflictReportJson } from './conflicts.js';
|
|
8
9
|
export { stampId, parseIdParts, namespaceStyleFor, isNamespacedFor } from './namespace.js';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// `rcf define blueprint remove-resolution <adr-id>` implementation.
|
|
2
|
+
//
|
|
3
|
+
// Removes a single entry from `manifest.resolutions[]` (and NOTHING
|
|
4
|
+
// else). The `<adr-id>` argument is matched against the entry's
|
|
5
|
+
// `resolvedByAdrId` field, which is how the doctor's probe-path-owner
|
|
6
|
+
// check (spec section 9) and the four-path resolution card (spec
|
|
7
|
+
// section 4) name the resolution to the operator.
|
|
8
|
+
//
|
|
9
|
+
// Behaviours (spec amendment A2, ratified 2026-09-04):
|
|
10
|
+
// - Removes the resolutions[] entry whose `resolvedByAdrId` equals
|
|
11
|
+
// the argument; leaves every other manifest section untouched. The
|
|
12
|
+
// project-level ADR file at `rcf/adrs/<adr-id>.json` is NOT
|
|
13
|
+
// deleted: an operator who wants to keep the ruling ADR as
|
|
14
|
+
// historical context after the redundant resolution goes away has
|
|
15
|
+
// that path open; an operator who wants the ADR gone can rm it
|
|
16
|
+
// themselves.
|
|
17
|
+
// - Refuses exit 2 when the argument is not a resolution entry on
|
|
18
|
+
// this manifest. Two flavours count as "not a resolution entry":
|
|
19
|
+
// the id is malformed (fails the ADR-\d{3,}(-<kebab-tail>)? grammar),
|
|
20
|
+
// or the id is well-formed but names no ADR anywhere on the
|
|
21
|
+
// tree AND is not present under any resolutions[] entry.
|
|
22
|
+
// - Idempotent on a second run: when the id is a well-formed ADR
|
|
23
|
+
// id that names an ADR present on the tree (the ruling ADR that
|
|
24
|
+
// the resolution had pointed at) but is not (any longer) present
|
|
25
|
+
// under any resolutions[] entry, the module returns
|
|
26
|
+
// `{ removed: false, alreadyAbsent: true }` and the CLI edge
|
|
27
|
+
// prints "nothing to remove" and exits 0. The distinction is:
|
|
28
|
+
// the ruling ADR still exists on disk, so the operator is running
|
|
29
|
+
// the SAME operation a second time, not typing a bogus id.
|
|
30
|
+
//
|
|
31
|
+
// The verb never touches project ADR files, blueprint records, or any
|
|
32
|
+
// other manifest section. That keeps the scope narrow enough that the
|
|
33
|
+
// operator can reason about the write without reading the module.
|
|
34
|
+
|
|
35
|
+
import { isRcfError, rcfError } from '../core/errors/index.js';
|
|
36
|
+
import { updateManifest } from './manifest-writer.js';
|
|
37
|
+
|
|
38
|
+
const ADR_ID_PATTERN = /^ADR-\d{3,}(?:-[a-z0-9]+(?:-[a-z0-9]+)*)?$/;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {object} RemoveResolutionResult
|
|
42
|
+
* @property {boolean} removed
|
|
43
|
+
* @property {boolean} [alreadyAbsent]
|
|
44
|
+
* @property {string} resolvedByAdrId
|
|
45
|
+
* @property {string} [resolutionId]
|
|
46
|
+
* @property {string} [topic]
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {object} args
|
|
51
|
+
* @param {string} args.projectRoot
|
|
52
|
+
* @param {import('#core/store/walker.js').TreeModel} args.tree
|
|
53
|
+
* @param {string} args.resolvedByAdrId
|
|
54
|
+
* @param {boolean} [args.dryRun]
|
|
55
|
+
* @returns {Promise<RemoveResolutionResult | import('../core/errors/index.js').RcfError>}
|
|
56
|
+
*/
|
|
57
|
+
export async function removeResolution({ projectRoot, tree, resolvedByAdrId, dryRun = false }) {
|
|
58
|
+
if (typeof resolvedByAdrId !== 'string' || resolvedByAdrId.trim().length === 0) {
|
|
59
|
+
return rcfError({ kind: 'usage', message: `<adr-id> is required (e.g. rcf define blueprint remove-resolution ADR-011-health-probes).` });
|
|
60
|
+
}
|
|
61
|
+
if (!ADR_ID_PATTERN.test(resolvedByAdrId)) {
|
|
62
|
+
return rcfError({ kind: 'usage', message: `'${resolvedByAdrId}' is not a well-formed ADR id (grammar: ADR-\\d{3,}(-<kebab-tail>)?).` });
|
|
63
|
+
}
|
|
64
|
+
const manifest = tree.manifest ?? {};
|
|
65
|
+
const resolutions = Array.isArray(manifest.resolutions) ? manifest.resolutions : [];
|
|
66
|
+
const index = resolutions.findIndex((r) => r?.resolvedByAdrId === resolvedByAdrId);
|
|
67
|
+
if (index === -1) {
|
|
68
|
+
// Not on resolutions[]. Two branches:
|
|
69
|
+
// - the ruling ADR file exists on the tree: idempotent no-op
|
|
70
|
+
// (this is a re-run of the same operation).
|
|
71
|
+
// - no ADR by that id anywhere: refuse. The operator has typed
|
|
72
|
+
// an id that this project has no record of, either as a
|
|
73
|
+
// resolution entry or as a project ADR.
|
|
74
|
+
const adrPresent = tree.byId instanceof Map && tree.byId.has(resolvedByAdrId);
|
|
75
|
+
if (adrPresent) {
|
|
76
|
+
return { removed: false, alreadyAbsent: true, resolvedByAdrId };
|
|
77
|
+
}
|
|
78
|
+
return rcfError({
|
|
79
|
+
kind: 'usage',
|
|
80
|
+
message: `'${resolvedByAdrId}' is not a resolution entry on this manifest (no resolutions[] record names it as resolvedByAdrId, and no ADR by that id exists on the project tree).`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const target = resolutions[index];
|
|
84
|
+
const result = await updateManifest({
|
|
85
|
+
projectRoot,
|
|
86
|
+
manifest,
|
|
87
|
+
mutate: (next) => {
|
|
88
|
+
const list = Array.isArray(next.resolutions) ? next.resolutions : [];
|
|
89
|
+
list.splice(index, 1);
|
|
90
|
+
// Drop the field entirely when empty so the manifest shape stays
|
|
91
|
+
// as compact as it was before any resolution was ever recorded.
|
|
92
|
+
if (list.length === 0) delete next.resolutions;
|
|
93
|
+
else next.resolutions = list;
|
|
94
|
+
},
|
|
95
|
+
dryRun,
|
|
96
|
+
});
|
|
97
|
+
if (isRcfError(result)) return result;
|
|
98
|
+
return {
|
|
99
|
+
removed: true,
|
|
100
|
+
resolvedByAdrId,
|
|
101
|
+
resolutionId: typeof target?.id === 'string' ? target.id : undefined,
|
|
102
|
+
topic: typeof target?.topic === 'string' ? target.topic : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
package/src/cli/blueprint.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
groupRowsByCategory,
|
|
21
21
|
listBlueprints,
|
|
22
22
|
removeBlueprint,
|
|
23
|
+
removeResolution,
|
|
23
24
|
renderDiff,
|
|
24
25
|
resolveBlueprintSource,
|
|
25
26
|
supersedeBlueprintTopic,
|
|
@@ -66,6 +67,20 @@ Verbs:
|
|
|
66
67
|
diff <topic> Side-by-side view of every applied blueprint's
|
|
67
68
|
scope:global ADR on <topic>: id, path, title,
|
|
68
69
|
status, decision. Read-only.
|
|
70
|
+
remove-resolution <adr-id>
|
|
71
|
+
Remove a single manifest.resolutions[] entry by
|
|
72
|
+
its resolvedByAdrId (the id the doctor's
|
|
73
|
+
probe-path-owner check names when a historical
|
|
74
|
+
resolution has become redundant after a
|
|
75
|
+
blueprint upgrade). Writes nothing else: the
|
|
76
|
+
project-level ADR file at rcf/adrs/<adr-id>.json
|
|
77
|
+
is left in place as historical context. Refuses
|
|
78
|
+
exit 2 when <adr-id> is malformed or names no
|
|
79
|
+
ADR on the project tree. Idempotent on re-run:
|
|
80
|
+
when <adr-id> is a well-formed ADR id that
|
|
81
|
+
names an ADR present on the tree but is no
|
|
82
|
+
longer on any resolutions[] entry, prints
|
|
83
|
+
"nothing to remove" and exits 0.
|
|
69
84
|
library <verb> Manage external blueprint libraries. Sub-verbs:
|
|
70
85
|
add, list, remove, refresh. See
|
|
71
86
|
'rcf define blueprint library --help' for the
|
|
@@ -364,6 +379,32 @@ export async function main(argv, deps = {}) {
|
|
|
364
379
|
return 0;
|
|
365
380
|
}
|
|
366
381
|
|
|
382
|
+
if (verb === 'remove-resolution') {
|
|
383
|
+
if (rest.length === 0) {
|
|
384
|
+
stderr.write('[error] blueprint remove-resolution: missing <adr-id>\n');
|
|
385
|
+
return 2;
|
|
386
|
+
}
|
|
387
|
+
const adrId = rest[0];
|
|
388
|
+
const result = await removeResolution({
|
|
389
|
+
projectRoot, tree, resolvedByAdrId: adrId,
|
|
390
|
+
dryRun: parsed.values['dry-run'] === true,
|
|
391
|
+
});
|
|
392
|
+
if (isRcfError(result)) {
|
|
393
|
+
stderr.write(`[error] blueprint remove-resolution: ${result.message}\n`);
|
|
394
|
+
return 2;
|
|
395
|
+
}
|
|
396
|
+
if (result.alreadyAbsent) {
|
|
397
|
+
if (!parsed.values.quiet) stdout.write(`[blueprint] nothing to remove: '${adrId}' is not on manifest.resolutions[].\n`);
|
|
398
|
+
return 0;
|
|
399
|
+
}
|
|
400
|
+
if (!parsed.values.quiet) {
|
|
401
|
+
const topicSuffix = result.topic ? ` (topic '${result.topic}')` : '';
|
|
402
|
+
const idSuffix = result.resolutionId ? `; dropped resolution ${result.resolutionId}` : '';
|
|
403
|
+
stdout.write(`[blueprint] removed resolution for '${adrId}'${topicSuffix}${idSuffix}.\n`);
|
|
404
|
+
}
|
|
405
|
+
return 0;
|
|
406
|
+
}
|
|
407
|
+
|
|
367
408
|
stderr.write(`[error] blueprint: unknown verb '${verb}'\n`);
|
|
368
409
|
stderr.write(HELP);
|
|
369
410
|
return 2;
|
package/src/cli/doctor.js
CHANGED
|
@@ -76,6 +76,7 @@ const KNOWN_CHECKS = /** @type {const} */ ([
|
|
|
76
76
|
'browser-present',
|
|
77
77
|
'playwright-mcp-reachable',
|
|
78
78
|
'playwright-mcp-redundant',
|
|
79
|
+
'probe-path-owner',
|
|
79
80
|
]);
|
|
80
81
|
|
|
81
82
|
/** The four Playwright-related checks doctor runs conditionally for
|
|
@@ -102,7 +103,7 @@ Options:
|
|
|
102
103
|
Values: agent-instructions, gitignore,
|
|
103
104
|
knowledge, identity, playwright-present,
|
|
104
105
|
browser-present, playwright-mcp-reachable,
|
|
105
|
-
playwright-mcp-redundant.
|
|
106
|
+
playwright-mcp-redundant, probe-path-owner.
|
|
106
107
|
--json Emit machine-readable envelope: { ok, drift, writes, notices }.
|
|
107
108
|
--quiet Only summary line + first 3 drift items.
|
|
108
109
|
--force Accept a legacy-markers --fix on hand-edited
|
|
@@ -247,6 +248,11 @@ export async function main(argv, deps = {}) {
|
|
|
247
248
|
// API-only project even under an explicit --check ask.
|
|
248
249
|
if (!ctx.browserFacing) continue;
|
|
249
250
|
result = await runPlaywrightMcpRedundantCheck(ctx);
|
|
251
|
+
} else if (check === 'probe-path-owner') {
|
|
252
|
+
// Fires whenever more than one applied blueprint teaches probe paths.
|
|
253
|
+
// Runs on every project (not gated on browser-facing). Spec:
|
|
254
|
+
// projects/rcf-lite-wsd/specs/rcf-lite-probe-path-alignment-spec-2026-09-04.md section 9.
|
|
255
|
+
result = await runProbePathOwnerCheck(ctx);
|
|
250
256
|
} else continue;
|
|
251
257
|
for (const d of result.drift) drift.push({ check, ...d });
|
|
252
258
|
for (const w of result.writes) writes.push(w);
|
|
@@ -705,6 +711,75 @@ async function runPlaywrightMcpRedundantCheck(ctx) {
|
|
|
705
711
|
return { drift, writes: [] };
|
|
706
712
|
}
|
|
707
713
|
|
|
714
|
+
/* ------------------------------------------------------------------ */
|
|
715
|
+
/* Check: probe-path-owner (probe-path alignment spec section 9) */
|
|
716
|
+
/* ------------------------------------------------------------------ */
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Fires when more than one applied blueprint teaches probe paths. Reads
|
|
720
|
+
* `rcf/manifest.json` for the applied-blueprint records; counts how many
|
|
721
|
+
* carry a scope:global ADR on `healthProbes` (the shelf-wide probe-path
|
|
722
|
+
* ownership topic). More than one is drift: names the blueprints and the
|
|
723
|
+
* remedy line naming the one that would have to drop its opinion.
|
|
724
|
+
*
|
|
725
|
+
* Also names any resolutions[] entry the alignment removed as redundant
|
|
726
|
+
* (a project that applied essentials v1.x + probe-endpoints v1.0.0 and
|
|
727
|
+
* resolved the two topics via supersede will have a resolution entry
|
|
728
|
+
* pointing at both blueprint ADRs; after re-applying to essentials v2.0.0
|
|
729
|
+
* the essentials side no longer claims the topic and the resolution is
|
|
730
|
+
* redundant historical context the operator can remove).
|
|
731
|
+
*/
|
|
732
|
+
async function runProbePathOwnerCheck(ctx) {
|
|
733
|
+
const drift = [];
|
|
734
|
+
const manifestPath = join(ctx.projectRoot, 'rcf', 'manifest.json');
|
|
735
|
+
let manifest = null;
|
|
736
|
+
try {
|
|
737
|
+
manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
738
|
+
} catch { return { drift, writes: [] }; }
|
|
739
|
+
const applied = Array.isArray(manifest?.blueprints) ? manifest.blueprints : [];
|
|
740
|
+
const claimants = { healthProbes: [], readinessSemantics: [] };
|
|
741
|
+
for (const b of applied) {
|
|
742
|
+
for (const c of b?.contributions ?? []) {
|
|
743
|
+
if (c?.kind === 'adr' && c?.scope === 'global' && (c.topic === 'healthProbes' || c.topic === 'readinessSemantics')) {
|
|
744
|
+
claimants[c.topic].push({ slug: b.slug, adrId: c.id });
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
for (const topic of ['healthProbes', 'readinessSemantics']) {
|
|
749
|
+
if (claimants[topic].length > 1) {
|
|
750
|
+
const slugs = claimants[topic].map((x) => x.slug).join(', ');
|
|
751
|
+
const owner = claimants[topic].find((x) => x.slug === 'observability-probe-endpoints');
|
|
752
|
+
const remedy = owner
|
|
753
|
+
? `${claimants[topic].filter((x) => x.slug !== 'observability-probe-endpoints').map((x) => x.slug).join(', ')} would have to drop its scope:global claim on ${topic} (observability-probe-endpoints is the shelf canonical owner from probe-endpoints v1.0.0)`
|
|
754
|
+
: `one of ${slugs} would have to drop its scope:global claim on ${topic}`;
|
|
755
|
+
drift.push({
|
|
756
|
+
item: `multiple-${topic}-owners`,
|
|
757
|
+
file: 'rcf/manifest.json',
|
|
758
|
+
message: `more than one applied blueprint teaches probe paths on topic '${topic}': ${slugs}. ${remedy}. Spec: projects/rcf-lite-wsd/specs/rcf-lite-probe-path-alignment-spec-2026-09-04.md section 9.`,
|
|
759
|
+
refusedByFix: true,
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// Redundant resolutions: a resolutions[] entry that names both
|
|
764
|
+
// `healthProbes` or `readinessSemantics` where the current claimant
|
|
765
|
+
// count is 1 (or 0) is redundant historical context.
|
|
766
|
+
const resolutions = Array.isArray(manifest?.resolutions) ? manifest.resolutions : [];
|
|
767
|
+
for (const r of resolutions) {
|
|
768
|
+
if (r?.topic === 'healthProbes' || r?.topic === 'readinessSemantics') {
|
|
769
|
+
if (claimants[r.topic].length <= 1) {
|
|
770
|
+
drift.push({
|
|
771
|
+
item: `redundant-${r.topic}-resolution`,
|
|
772
|
+
file: 'rcf/manifest.json',
|
|
773
|
+
message: `resolution '${r?.resolvedByAdrId ?? '(unnamed)'}' on topic '${r.topic}' is now redundant historical context: only ${claimants[r.topic].length} applied blueprint claims the topic after the probe-path alignment. Consider \`rcf define blueprint remove-resolution ${r?.resolvedByAdrId ?? '<id>'}\` or leave the resolution ADR as historical.`,
|
|
774
|
+
refusedByFix: true,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return { drift, writes: [] };
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
|
|
708
783
|
/**
|
|
709
784
|
* Default reader for the project-root .mcp.json body. Returns the parsed
|
|
710
785
|
* object, or null on missing / unparseable (doctor treats an unparseable
|