rcf-lite 0.14.0 → 0.15.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 +19 -0
- package/bin/view-supervisor-child.mjs +0 -0
- package/blueprints/delivery-ci-workflows/README.md +4 -0
- package/blueprints/delivery-ci-workflows/assets/bootstrap/README.md +26 -0
- package/blueprints/delivery-ci-workflows/assets/bootstrap/adr-bootstrap-coverage-supersession.template.json +28 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/default-branch-checks.yml +12 -6
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/pull-request-checks.yml +16 -7
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/release.yml +4 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/scheduled-audit.yml +4 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/notes.md +5 -5
- package/blueprints/delivery-ci-workflows/assets/report-samples/per-gate.json +1 -1
- package/blueprints/delivery-ci-workflows/blueprint.json +1 -1
- package/blueprints/delivery-ci-workflows/contributions/adrs/adr-702-delivery-ci-workflows-strict-coverage-gate.json +2 -2
- package/blueprints/delivery-ci-workflows/contributions/tacs/tac-701-delivery-ci-workflows-gate-runner.json +2 -2
- package/blueprints/delivery-ci-workflows/contributions/tacs/tac-704-delivery-ci-workflows-workflow-materialiser.json +7 -4
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6111.json +2 -2
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6114.json +6 -6
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6115.json +6 -6
- package/blueprints/delivery-ci-workflows/guide/delivery-ci-workflows.md +39 -1
- package/package.json +13 -15
- package/releases/releases.yaml +11 -1
- package/src/blueprint/apply.js +15 -6
- package/src/blueprint/library-loader.js +21 -0
- package/src/blueprint/supersede.js +56 -13
- package/src/cli/blueprint-library.js +30 -2
- package/src/cli/blueprint.js +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Pre-1.0, breaking changes are signalled by a minor version bump.
|
|
6
6
|
|
|
7
|
+
## [0.15.0] - 2026-08-31
|
|
8
|
+
|
|
9
|
+
Same-evening fix wave on top of 0.14.0. Four PRs closing off the WSD-handover surface: durable library-ownership stamping on applied records (paired with `@stravica-ai/rcf-schemas` 0.5.1), `blueprint supersede --incoming` accepting the same source forms as `add`, the review-on-add card gaining its section 8.1 global-topics and prefix-check lines, and the `delivery-ci-workflows` v2.1 additive shape resolving the four P1 findings from the second dogfood run.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`libraryPrefix` stamped on library-applied records** (#131, paired with `@stravica-ai/rcf-schemas` 0.5.1). `manifest.blueprints[]` records that resolved through a registered external library now carry the additive `libraryPrefix` field. `rcf define blueprint library remove` reads it as the authoritative ownership signal, with a source-prefix fallback for pre-0.5.1 records, so a library re-registered on this project under a different prefix (rename, split, publisher change) no longer orphans its previously applied records. Shelf and path applies still write no `libraryPrefix`.
|
|
14
|
+
- **Review-on-add renders section 8.1 lines** (#134, `w-2026-08-31-dave-026`). `rcf define blueprint library add` (interactive review) now prints a "Global topics these blueprints claim" section listing every `<libraryPrefix>:<blueprintSlug>` on the library that carries a `scope:global` ADR contribution, one topic per line, suppressed entirely when no blueprint claims a topic. A new unconditional "Prefix check" line reports the collision-gate outcome alongside the existing "Band check" line. Companion loader change attaches a deduped `globalTopics[]` per blueprint entry under `validateBlueprints: true`; the resolver-time hot path stays cheap.
|
|
15
|
+
- **`delivery-ci-workflows` v2.1 (additive workflowShape)** (#135). Blueprint version bumped from 2.0.0 to 2.1.0. Three new optional `workflowShape` fields land: `packageManager` (elicited, `pnpm` default, `pnpm` / `npm` / `yarn` / `bun` recognised) and `defaultBranch` + `trunkBranch` (both default `main`). The `rcf define blueprint add delivery-ci-workflows` guide teaches the per-manager setup and install blocks; the shipped GHA assets under `assets/ci-provider-examples/github-actions/` carry paired `# @@RCF-SUB-*@@` markers so the materialiser can substitute per project. A new "Bootstrap posture (the guaranteed-red first run)" section in the guide and README, plus a shipped `assets/bootstrap/adr-bootstrap-coverage-supersession.template.json` starting-point ADR, name and demote the coverage-strict bootstrap trap.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **`rcf define blueprint supersede --incoming` accepts every source form `add` accepts** (#133). Persona re-run arc-4 H2 showed `@stock/<slug>` and bare kebab slugs both hit `--incoming @stock/security-auth-magic-link: no blueprint.json found` because the verb fed the raw string to `loadBlueprint` (paths only) and skipped `resolveBlueprintSource`. `supersede` now routes `--incoming` through the shared resolver, so path, `@stock/<slug>`, bare kebab slug, and colon-qualified `<libraryPrefix>:<slug>` all work verbatim. Library-qualified sources record `effectiveSlug` in the supersedes[] entry, matching `apply.js`'s identity rewiring.
|
|
20
|
+
- **`delivery-ci-workflows` v2.1 four P1 dogfood-2 findings** (#135). F-3: TAC-701 interface descriptor + `assets/report-samples/per-gate.json` synced to the runnable grouped-verb form (`rcf define validate`, `rcf audit coverage --strict`). F-1: package manager is elicited and observable in the material output; alternate-provider notes teach the substitution. F-2: the branch-model AC property (branch model determines which branch triggers the workflow) is now observable in the material output rather than masked by a hard-coded `main`. F-4: the guaranteed-red-first-run trap is named at the guide, README, ADR-702 consequences, and a shipped bootstrap template. Ride-along F-5: TAC-704 responsibilities now name the source-resolution path (`manifest.blueprints[].source` -> `<source>/assets/ci-provider-examples/<providerHint>/`) so a first-time realiser is not left to invent it.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **`@stravica-ai/rcf-schemas` bumped from `0.5.0` to `0.5.1`, exact-pinned.** The additive `libraryPrefix` field on applied records requires the paired schema. Per the packaging registry runbook's version-skew doctrine, the dependency is an exact pin (`"0.5.1"`, never a caret or tilde); the caret introduced mid-cycle in #131 is corrected in this release.
|
|
25
|
+
|
|
7
26
|
## [0.14.0] - 2026-08-31
|
|
8
27
|
|
|
9
28
|
The WSD-handover release. Nine PRs on top of 0.13.0 covering update-awareness end-to-end (feed source-of-truth + validator, `rcf version --check`, RULE 14), the `ci-pipeline` rename to `delivery-ci-workflows`, external-blueprint-library resolution (phase 2b), scoped deep links in the live docs viewer, product-identity rename to "RCF Lite", and one integration-review fix batch.
|
|
File without changes
|
|
@@ -44,6 +44,10 @@ ADR-709-delivery-ci-workflows-release-artefacts ships `scope: global` on topic `
|
|
|
44
44
|
|
|
45
45
|
See `docs/topics.md` for the exact strings, the expected resolutions, the delineation from the application-api-rest blueprint's `logging` topic, and the AC id band allocation (delivery-ci-workflows owns 6101-6899).
|
|
46
46
|
|
|
47
|
+
## Bootstrap posture (loud)
|
|
48
|
+
|
|
49
|
+
A fresh apply of this blueprint alongside its usual companions contributes tens to hundreds of ACs and zero TCs. The mandatory tier's `coverage-strict` gate refuses when any AC lacks a resolving TC (ADR-702), so the FIRST commit-triggered workflow the materialiser produces is guaranteed to refuse until the project either authors every TC or ships a project-level ADR demoting the gate. The guide's "Bootstrap posture" section names the two ratified paths and points at the starting-point ADR at `assets/bootstrap/adr-bootstrap-coverage-supersession.template.json`. Choose a path at apply time; discovering the trap at the first PR is a real cost this warning exists to save.
|
|
50
|
+
|
|
47
51
|
## Quality bar
|
|
48
52
|
|
|
49
53
|
The workflow-materialiser reads `.rcf/config/delivery-ci-workflows.json` and refuses to run when the three required fields are missing or when any field carries an unrecognised value; the required check set runs on every commit-triggered workflow the branch model defines; the mandatory tier runs first in order (`validate` then `coverage-strict`); the elicited tier runs after the mandatory tier in stable but non-load-bearing order; per-gate JSON reports at stable paths carry the v1 fixed key set plus the v2 `checkKind` field; distinct workflows write to distinct aggregate paths (`.rcf/reports/ci/pipeline.json` for commit-triggered, `.rcf/reports/ci/release.json` for release, `.rcf/reports/ci/scheduled-audit.json` for scheduled); a missing required gate report is recorded as `outcome: missing` and flips the aggregate to `failed`; the release workflow shape scales across four discrete modes with the `none` semantic reached either by explicit value or by absent field; the `deployHandoff:<slug>` mode invokes the named deploy blueprint's `promote` workflow with the `versionId` input and refuses at boot when the deploy blueprint is absent; the applied blueprint's source path carries one illustrative GHA workflow file per workflow in the matrix and a notes file mapping four points to alternate providers. Every bar is carried by ACs in the doc set, not by this README.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Bootstrap assets
|
|
2
|
+
|
|
3
|
+
Templates the operator copies into their project during the bootstrap window
|
|
4
|
+
of a fresh delivery-ci-workflows apply. Nothing here is imported automatically
|
|
5
|
+
by the blueprint; every file is a starting point the operator adapts and
|
|
6
|
+
registers on the project tree.
|
|
7
|
+
|
|
8
|
+
## `adr-bootstrap-coverage-supersession.template.json`
|
|
9
|
+
|
|
10
|
+
Project-level ADR template that demotes the mandatory-tier `coverage-strict`
|
|
11
|
+
gate to advisory-only during the bootstrap window (see the guide's "Bootstrap
|
|
12
|
+
posture" section for the problem statement).
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
|
|
16
|
+
1. Copy this file into the project's ADR directory (typically `rcf/adrs/`).
|
|
17
|
+
2. Rename the file and its `adrId` from the `ADR-XXX-...` placeholder to the
|
|
18
|
+
next free project-level ADR id.
|
|
19
|
+
3. Replace the two `TEMPLATE-*ISO8601` timestamps with the current wall-clock.
|
|
20
|
+
4. Register the ADR on the project as `scope: global` on topic
|
|
21
|
+
`strictCoverageGate` so it competes with ADR-702 through the resolutions
|
|
22
|
+
mechanism.
|
|
23
|
+
5. Amend the exit criterion in the `decision` section if `N = 5` consecutive
|
|
24
|
+
passes is not the right threshold for the project.
|
|
25
|
+
6. When the exit criterion is met, flip `status` to `superseded` and leave
|
|
26
|
+
the file in place for audit trail.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-XXX-bootstrap-coverage-supersession",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"title": "During bootstrap the coverage-strict gate runs advisory-only until a stated exit criterion is met",
|
|
8
|
+
"context": "A fresh apply of the delivery-ci-workflows blueprint set (application-*, persistence-*, delivery-ci-workflows) contributes tens to hundreds of REQs and ACs and zero test cases. The mandatory-tier coverage-strict gate runs `rcf audit coverage --strict` which refuses when any AC lacks a resolving TC (ADR-702). On a fresh apply the very first commit-triggered workflow is therefore guaranteed to refuse until the project has authored a TC for every AC the blueprint set contributed. That is a real cost the operator either accepts up-front (author every TC before turning CI green) or defers behind a project-level supersession of ADR-702. This project accepts the deferral posture and states the exit criterion here.",
|
|
9
|
+
"decision": "During bootstrap the required-gate coverage invocation is demoted to advisory-only: the coverage-strict gate still runs on every commit-triggered workflow, its per-gate report still lands at the stable path, and the aggregate still records its outcome; but a `failed` outcome on coverage-strict does NOT flip the aggregate verdict to `failed` and does NOT block merge. Every other required gate (validate, and the elicited tier the project turned on) retains its merge-blocking semantics. This ADR supersedes ADR-702-delivery-ci-workflows-strict-coverage-gate on the `strictCoverageGate` topic for this project only. The demotion is bounded by a stated exit criterion: this ADR is retired (status flipped to superseded) when the mandatory-tier coverage-strict outcome has been `passed` on the default branch for N consecutive commits (recommended: N = 5) OR when the project's own audit shows every AC in the applied blueprint set has a resolving TC, whichever comes first. On retirement the coverage-strict gate reverts to merge-blocking per ADR-702's default posture, and this ADR's file is left in place with `status: superseded` for audit trail.",
|
|
10
|
+
"consequences": "The bootstrap window is a knowingly-relaxed CI posture; a reviewer merging during it MUST inspect the aggregate's coverage-strict per-gate report themselves rather than trusting the aggregate verdict alone. Every AC the blueprint set contributed still owes a TC; the exit criterion enforces the debt is repaid rather than allowed to accrue silently. A composing blueprint that ships its own coverage-mode opinion (a hypothetical `delivery-ci-workflows-shallow` or `delivery-ci-workflows-grace-window`) conflicts with this ADR the same way it would with ADR-702; the project-level resolver still picks the winner. Retiring this ADR is a one-line status change; the exit criterion is a checklist the retiring commit records.",
|
|
11
|
+
"alternativesConsidered": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Author every TC before turning CI green",
|
|
14
|
+
"summary": "Do not demote the gate; author the ~N hundred TCs before the first CI run.",
|
|
15
|
+
"reasonNotChosen": "The blueprint set is large and TC authoring is a real cost; front-loading it delays every other CI signal the project needs from day one. The demote-with-exit-criterion posture keeps every OTHER gate merge-blocking from day one and pays the coverage debt on a stated schedule."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Demote coverage-strict permanently to shallow-any",
|
|
19
|
+
"summary": "Retire ADR-702 without an exit criterion; run shallow-any as the project's coverage floor forever.",
|
|
20
|
+
"reasonNotChosen": "ADR-702's own consequences section names the failure mode shallow-any hides (an AC bound but no TC resolves it, REQ reads as covered because a sibling AC's TC resolves). The bootstrap posture is a stated deferral, not a permanent policy change; retiring the deferral is a one-line change the exit criterion drives."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"relatedAdrs": [
|
|
24
|
+
"ADR-702-delivery-ci-workflows-strict-coverage-gate"
|
|
25
|
+
],
|
|
26
|
+
"createdAt": "TEMPLATE-createdAt-ISO8601",
|
|
27
|
+
"updatedAt": "TEMPLATE-updatedAt-ISO8601"
|
|
28
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# Illustrative default-branch-checks workflow for the delivery-ci-workflows blueprint.
|
|
2
|
-
# Copy this file into the host project as `.github/workflows/default-branch-checks.yml
|
|
3
|
-
#
|
|
4
|
-
#
|
|
2
|
+
# Copy this file into the host project as `.github/workflows/default-branch-checks.yml`.
|
|
3
|
+
# The file below is the pnpm+main default the materialiser writes when
|
|
4
|
+
# workflowShape.packageManager, workflowShape.defaultBranch and
|
|
5
|
+
# workflowShape.trunkBranch are absent.
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# The file carries paired RCF substitution markers the materialiser
|
|
8
|
+
# rewrites from workflowShape (see guide/delivery-ci-workflows.md for
|
|
9
|
+
# marker names, replacement blocks, and the substitution table).
|
|
9
10
|
#
|
|
10
11
|
# The workflow runs the same required check set as pull-request-checks and
|
|
11
12
|
# writes to the same aggregate report path `.rcf/reports/ci/pipeline.json`.
|
|
@@ -17,6 +18,7 @@ name: default-branch-checks
|
|
|
17
18
|
|
|
18
19
|
on:
|
|
19
20
|
push:
|
|
21
|
+
# @@RCF-SUB-BRANCH-NAME@@
|
|
20
22
|
branches: [main]
|
|
21
23
|
|
|
22
24
|
jobs:
|
|
@@ -28,6 +30,7 @@ jobs:
|
|
|
28
30
|
- name: checkout
|
|
29
31
|
uses: actions/checkout@v4
|
|
30
32
|
|
|
33
|
+
# @@RCF-SUB-PKG-MGR-SETUP-BEGIN@@
|
|
31
34
|
- name: setup pnpm
|
|
32
35
|
uses: pnpm/action-setup@v4
|
|
33
36
|
with:
|
|
@@ -38,9 +41,12 @@ jobs:
|
|
|
38
41
|
with:
|
|
39
42
|
node-version: 24
|
|
40
43
|
cache: pnpm
|
|
44
|
+
# @@RCF-SUB-PKG-MGR-SETUP-END@@
|
|
41
45
|
|
|
46
|
+
# @@RCF-SUB-PKG-MGR-INSTALL-BEGIN@@
|
|
42
47
|
- name: install dependencies
|
|
43
48
|
run: pnpm install --frozen-lockfile
|
|
49
|
+
# @@RCF-SUB-PKG-MGR-INSTALL-END@@
|
|
44
50
|
|
|
45
51
|
- name: run rcf check set
|
|
46
52
|
run: node scripts/rcf-ci.js
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
# Illustrative pull-request-checks workflow for the delivery-ci-workflows blueprint.
|
|
2
|
-
# Copy this file into the host project as `.github/workflows/pull-request-checks.yml
|
|
3
|
-
#
|
|
4
|
-
#
|
|
2
|
+
# Copy this file into the host project as `.github/workflows/pull-request-checks.yml`.
|
|
3
|
+
# The file below is the pnpm+main default the materialiser writes when
|
|
4
|
+
# workflowShape.packageManager and workflowShape.defaultBranch are absent.
|
|
5
|
+
#
|
|
6
|
+
# The file carries paired RCF substitution markers the materialiser rewrites
|
|
7
|
+
# from workflowShape (see guide/delivery-ci-workflows.md, "Within-provider
|
|
8
|
+
# package-manager substitution" and "Trigger branch-name substitution" for
|
|
9
|
+
# marker names, replacement blocks, and the substitution table). A
|
|
10
|
+
# hand-copier on the default (pnpm + main) keeps the file as-is; a project
|
|
11
|
+
# on npm/yarn/bun or a non-main branch either runs the materialiser or
|
|
12
|
+
# applies the substitution table from the guide by hand.
|
|
5
13
|
#
|
|
6
14
|
# The workflow runs one CI-provider-neutral Node entry point that invokes the
|
|
7
15
|
# required check set (the mandatory tier plus the elicited subset the project
|
|
@@ -9,10 +17,6 @@
|
|
|
9
17
|
# reports under `.rcf/reports/ci/`, and uploads the report directory as a
|
|
10
18
|
# workflow artefact for downstream readers.
|
|
11
19
|
#
|
|
12
|
-
# Feature-branch shape: pull requests targeting the default branch.
|
|
13
|
-
# Trunk-based shape (trunkPullRequests: sometimes): pull requests targeting
|
|
14
|
-
# the trunk branch. Adjust the branch name on the `on: pull_request:` block.
|
|
15
|
-
#
|
|
16
20
|
# See `guide/delivery-ci-workflows.md` (four-point mapping) for translating
|
|
17
21
|
# this workflow to GitLab CI, CircleCI, Buildkite, or Jenkins.
|
|
18
22
|
|
|
@@ -20,6 +24,7 @@ name: pull-request-checks
|
|
|
20
24
|
|
|
21
25
|
on:
|
|
22
26
|
pull_request:
|
|
27
|
+
# @@RCF-SUB-BRANCH-NAME@@
|
|
23
28
|
branches: [main]
|
|
24
29
|
|
|
25
30
|
jobs:
|
|
@@ -31,6 +36,7 @@ jobs:
|
|
|
31
36
|
- name: checkout
|
|
32
37
|
uses: actions/checkout@v4
|
|
33
38
|
|
|
39
|
+
# @@RCF-SUB-PKG-MGR-SETUP-BEGIN@@
|
|
34
40
|
- name: setup pnpm
|
|
35
41
|
uses: pnpm/action-setup@v4
|
|
36
42
|
with:
|
|
@@ -41,9 +47,12 @@ jobs:
|
|
|
41
47
|
with:
|
|
42
48
|
node-version: 24
|
|
43
49
|
cache: pnpm
|
|
50
|
+
# @@RCF-SUB-PKG-MGR-SETUP-END@@
|
|
44
51
|
|
|
52
|
+
# @@RCF-SUB-PKG-MGR-INSTALL-BEGIN@@
|
|
45
53
|
- name: install dependencies
|
|
46
54
|
run: pnpm install --frozen-lockfile
|
|
55
|
+
# @@RCF-SUB-PKG-MGR-INSTALL-END@@
|
|
47
56
|
|
|
48
57
|
# Single-line invocation of the project's realised gate runner.
|
|
49
58
|
# The runner spawns the mandatory tier (`rcf define validate` then
|
package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/release.yml
CHANGED
|
@@ -35,6 +35,7 @@ jobs:
|
|
|
35
35
|
- name: checkout
|
|
36
36
|
uses: actions/checkout@v4
|
|
37
37
|
|
|
38
|
+
# @@RCF-SUB-PKG-MGR-SETUP-BEGIN@@
|
|
38
39
|
- name: setup pnpm
|
|
39
40
|
uses: pnpm/action-setup@v4
|
|
40
41
|
with:
|
|
@@ -45,9 +46,12 @@ jobs:
|
|
|
45
46
|
with:
|
|
46
47
|
node-version: 24
|
|
47
48
|
cache: pnpm
|
|
49
|
+
# @@RCF-SUB-PKG-MGR-SETUP-END@@
|
|
48
50
|
|
|
51
|
+
# @@RCF-SUB-PKG-MGR-INSTALL-BEGIN@@
|
|
49
52
|
- name: install dependencies
|
|
50
53
|
run: pnpm install --frozen-lockfile
|
|
54
|
+
# @@RCF-SUB-PKG-MGR-INSTALL-END@@
|
|
51
55
|
|
|
52
56
|
# The release orchestrator reads the releaseMode from its environment
|
|
53
57
|
# (RCF_RELEASE_MODE) and runs the appropriate step sequence:
|
|
@@ -31,6 +31,7 @@ jobs:
|
|
|
31
31
|
- name: checkout
|
|
32
32
|
uses: actions/checkout@v4
|
|
33
33
|
|
|
34
|
+
# @@RCF-SUB-PKG-MGR-SETUP-BEGIN@@
|
|
34
35
|
- name: setup pnpm
|
|
35
36
|
uses: pnpm/action-setup@v4
|
|
36
37
|
with:
|
|
@@ -41,9 +42,12 @@ jobs:
|
|
|
41
42
|
with:
|
|
42
43
|
node-version: 24
|
|
43
44
|
cache: pnpm
|
|
45
|
+
# @@RCF-SUB-PKG-MGR-SETUP-END@@
|
|
44
46
|
|
|
47
|
+
# @@RCF-SUB-PKG-MGR-INSTALL-BEGIN@@
|
|
45
48
|
- name: install dependencies
|
|
46
49
|
run: pnpm install --frozen-lockfile
|
|
50
|
+
# @@RCF-SUB-PKG-MGR-INSTALL-END@@
|
|
47
51
|
|
|
48
52
|
# The scheduled runner is a thin wrapper around the gate-runner
|
|
49
53
|
# (TAC-701) that overrides the aggregate report path to the
|
|
@@ -15,7 +15,7 @@ Which workflow files exist depends on `workflowShape`:
|
|
|
15
15
|
|
|
16
16
|
1. Job trigger. Fire the workflow's job on the trigger set defined for that workflow (see the corresponding GHA file's `on:` block for the reference shape).
|
|
17
17
|
2. Node setup. Install Node 24 or later on the runner.
|
|
18
|
-
3. Package-manager setup and install. Install the project's dependencies with the frozen-lockfile discipline of whichever manager the project uses.
|
|
18
|
+
3. Package-manager setup and install. Install the project's dependencies with the frozen-lockfile discipline of whichever manager the project uses. The manager is a project decision recorded on `workflowShape.packageManager` (default `pnpm`; recognised set `pnpm`, `npm`, `yarn`, `bun`); the per-manager setup and install blocks are named in the guide's "Within-provider package-manager substitution" table. Alternate providers translate the block to the provider's own action/command; the manager choice stays project-controlled.
|
|
19
19
|
4. Node entry-point invocation and artefact upload. Run `node <path>` (or a package-manager script that resolves to the same) for the workflow's entry point (gate-runner for `pull-request-checks` and `default-branch-checks`, release orchestrator for `release`, scheduled-audit runner for `scheduled-audit`); upload the aggregate report directory as a run artefact for downstream readers.
|
|
20
20
|
|
|
21
21
|
The blueprint does not ship provider-specific configuration files for the providers below at v2.0.0. A project hand-authors the first three points in the provider's own runner language and keeps the fourth unchanged per the workflow's entry point.
|
|
@@ -24,7 +24,7 @@ The blueprint does not ship provider-specific configuration files for the provid
|
|
|
24
24
|
|
|
25
25
|
- Triggers: for `pull-request-checks` use `rules: { if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main" }`; for `default-branch-checks` use `rules: { if: $CI_COMMIT_BRANCH == "main" }`; for `release` use `rules: { if: $CI_COMMIT_TAG }` plus a `workflow_dispatch`-equivalent through GitLab's manual jobs; for `scheduled-audit` use a scheduled pipeline.
|
|
26
26
|
- Node setup: use a `node:24` image or install through the runner's setup hook.
|
|
27
|
-
- Install: `pnpm install --frozen-lockfile`
|
|
27
|
+
- Install: the install line from the guide's substitution table for the project's `workflowShape.packageManager` (default `pnpm install --frozen-lockfile`).
|
|
28
28
|
- Invoke: `node scripts/rcf-ci.js` for check-set workflows; `node scripts/rcf-release.js` for release; `node scripts/rcf-scheduled-audit.js --report-path .rcf/reports/ci/scheduled-audit.json` for scheduled-audit.
|
|
29
29
|
- Artefact upload: `artifacts: { when: always, paths: [.rcf/reports/ci/] }`.
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ The blueprint does not ship provider-specific configuration files for the provid
|
|
|
32
32
|
|
|
33
33
|
- Triggers: `workflows:` with branch and PR filters for the two check-set workflows; scheduled workflows for `scheduled-audit`; the release workflow fires from tag-triggered pipelines.
|
|
34
34
|
- Node setup: `cimg/node:24.19` or the `node/install` orb command.
|
|
35
|
-
- Install:
|
|
35
|
+
- Install: the install line from the guide's substitution table for the project's `workflowShape.packageManager`.
|
|
36
36
|
- Invoke: as above.
|
|
37
37
|
- Artefact upload: `store_artifacts: { path: .rcf/reports/ci }`.
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@ The blueprint does not ship provider-specific configuration files for the provid
|
|
|
40
40
|
|
|
41
41
|
- Triggers: pipeline-level branch conditions or per-step `branches: main` for check-set workflows; separate pipelines for release (tag-triggered) and scheduled-audit (Buildkite scheduled builds).
|
|
42
42
|
- Node setup: install Node 24 in the queue's setup hook or use a Docker plugin (`docker#v5.0.0`).
|
|
43
|
-
- Install:
|
|
43
|
+
- Install: the install line from the guide's substitution table for the project's `workflowShape.packageManager`.
|
|
44
44
|
- Invoke: as above.
|
|
45
45
|
- Artefact upload: `artifact_paths: [".rcf/reports/ci/**"]`.
|
|
46
46
|
|
|
@@ -48,7 +48,7 @@ The blueprint does not ship provider-specific configuration files for the provid
|
|
|
48
48
|
|
|
49
49
|
- Triggers: `triggers { githubPush() }` plus a multibranch pipeline configured to build pull requests against the default branch; a separate tag-triggered pipeline for release; `triggers { cron('H 6 * * *') }` for scheduled-audit.
|
|
50
50
|
- Node setup: install Node 24 via `tools { nodejs '24' }` or provision the agent with Node preinstalled.
|
|
51
|
-
- Install: `sh
|
|
51
|
+
- Install: `sh` invocation of the install line from the guide's substitution table for the project's `workflowShape.packageManager`.
|
|
52
52
|
- Invoke: `sh 'node scripts/rcf-ci.js'` (or the appropriate script per workflow).
|
|
53
53
|
- Artefact upload: `archiveArtifacts artifacts: '.rcf/reports/ci/**', allowEmptyArchive: true`.
|
|
54
54
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"title": "Coverage runs as per-AC strict; shallow-any is not accepted as a substitute in the required gate set",
|
|
8
8
|
"context": "The `rcf audit coverage` CLI runs in two modes: the default `shallow-any` (a REQ counts as covered when any AC on any US has any resolving TC) and the opt-in `--strict` (per-AC: every AC on every US must have a resolving TC). Shallow-any is useful for author-time exploration; it is the wrong mode for a merge-blocking gate because it hides the failure mode watchpost run4 caught in categories 5, 6, and 11 (an AC bound but no TC resolves it, and the REQ still reads as covered because some other AC's TC resolved). Choosing which coverage mode the pipeline runs is a policy decision distinct from choosing whether coverage runs at all.",
|
|
9
9
|
"decision": "The required-gate coverage invocation is `rcf audit coverage --strict`. Every AC on every US must resolve to a TC whose testPointer points at a real test; a `covered-unresolved` or uncovered AC fails the gate. Shallow-any is not accepted as a substitute in the required-gate set. A project that needs an author-time shallow-any view for exploration runs `rcf audit coverage` locally without `--strict`; the pipeline's gate is unaffected. Composing blueprints that ship a coverage gate hold this topic (`strictCoverageGate`) and conflict here by design: a hypothetical `delivery-ci-workflows-shallow` blueprint or a project that wants the shallow-any mode as its floor supersedes this ADR with a project-level ADR stating the reasoning.",
|
|
10
|
-
"consequences": "Every AC the RCF chain contributes must have a TC bound in the project's own build cycle before the pipeline turns green. That is the mechanism-reach discipline the strict mode enforces: shipping an AC without a TC is caught here, not in production. Composing blueprints (this one and any coverage-shipping blueprint the ecosystem grows) conflict on `strictCoverageGate` for one project-level ADR to fix. A project that thoughtfully accepts a shallow-any floor (small project, exploration phase, no operator-facing surface yet) authors that ADR and the pipeline follows.",
|
|
10
|
+
"consequences": "Every AC the RCF chain contributes must have a TC bound in the project's own build cycle before the pipeline turns green. That is the mechanism-reach discipline the strict mode enforces: shipping an AC without a TC is caught here, not in production. Composing blueprints (this one and any coverage-shipping blueprint the ecosystem grows) conflict on `strictCoverageGate` for one project-level ADR to fix. A project that thoughtfully accepts a shallow-any floor (small project, exploration phase, no operator-facing surface yet) authors that ADR and the pipeline follows.\n\nBootstrap consequence (loud): a fresh apply of the delivery-ci-workflows blueprint alongside any doc-heavy companion (an application-* blueprint plus a persistence-* blueprint contributes tens to hundreds of ACs and zero TCs) means the very first commit-triggered workflow the materialiser produces is guaranteed to refuse on this gate until either every AC has a resolving TC OR a project-level ADR demotes the gate. The guide's \"Bootstrap posture\" section names the two ratified paths (author-every-TC-first, or demote-to-advisory-with-exit-criterion) and points at the template ADR at `assets/bootstrap/adr-bootstrap-coverage-supersession.template.json` that expresses the demotion posture. A first-time operator who reaches this ADR through the aggregate-report refusal at their first PR has hit the trap; a first-time operator who reads the guide's bootstrap section before running the materialiser makes the posture choice at apply time.",
|
|
11
11
|
"alternativesConsidered": [
|
|
12
12
|
{
|
|
13
13
|
"name": "Run shallow-any as the required mode with strict as a warning gate",
|
|
@@ -21,5 +21,5 @@
|
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
23
|
"createdAt": "2026-08-24T00:00:00Z",
|
|
24
|
-
"updatedAt": "2026-08-
|
|
24
|
+
"updatedAt": "2026-08-31T12:00:00Z"
|
|
25
25
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
{
|
|
27
27
|
"name": "gate-descriptor",
|
|
28
28
|
"kind": "value",
|
|
29
|
-
"description": "{ name: string, command: string[] } where `command[0]` is the executable (typically `rcf`) and the rest are argv. The
|
|
29
|
+
"description": "{ name: string, command: string[] } where `command[0]` is the executable (typically `rcf`) and the rest are argv. The rcf CLI groups verbs by RCF stage; the mandatory tier for v1.0.0 uses the grouped forms this TAC's responsibilities section names: `[{ name: 'validate', command: ['rcf', 'define', 'validate'] }, { name: 'coverage-strict', command: ['rcf', 'audit', 'coverage', '--strict'] }]`. A descriptor whose `command` array omits the group (`['rcf', 'validate']`, `['rcf', 'coverage', '--strict']`) does not resolve on any shipped rcf CLI and refuses at gate spawn; the gate-runner does not paper over the difference."
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"name": "entry-point CLI",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
],
|
|
56
56
|
"tradeoffs": "Running every gate as a separate child process costs Node startup for each; that cost buys a stable exit-code contract and a stable stream capture surface that a library-mode invocation would obscure. Continuing past a failing gate costs a slightly longer pipeline; that cost buys the reviewer seeing every failure on one run instead of chasing them one at a time. The runner does not run gates in parallel: gate ordering is a stable property the aggregate report depends on, and the small gate count at v1.0.0 does not motivate a parallel-and-serialise design.",
|
|
57
57
|
"createdAt": "2026-08-24T00:00:00Z",
|
|
58
|
-
"updatedAt": "2026-08-
|
|
58
|
+
"updatedAt": "2026-08-31T00:00:00Z"
|
|
59
59
|
}
|
|
@@ -11,9 +11,12 @@
|
|
|
11
11
|
"Perform the boot-check: refuse missing required fields (`branchModel`, `checkSet`, `providerHint`), refuse unrecognised values on any field, refuse `deployHandoff:<slug>` when the deploy blueprint is absent from the manifest; exit non-zero with a stable-coded error naming the offending field (AC-6112-1, AC-6112-2, AC-6122-3).",
|
|
12
12
|
"Expand a profile alias to the four-field composed form before any downstream reads it; refuse an unrecognised alias (AC-6113-1, AC-6113-2).",
|
|
13
13
|
"Compute the workflow set from `workflowShape` per the matrix in the spec (§3.2): the two commit-triggered workflows per branch model, the release workflow per `releaseMode`, the scheduled-audit workflow per `scheduledAudit` (AC-6114-1, AC-6114-2, AC-6115-1, AC-6115-2, AC-6121-1, AC-6121-2, AC-6123-1, AC-6123-2).",
|
|
14
|
-
"
|
|
14
|
+
"Read the applied blueprint source from `rcf/manifest.json`'s `blueprints[].source` field (the path recorded when `rcf define blueprint add` ran) and resolve the provider asset directory as `<source>/assets/ci-provider-examples/<providerHint>/`; do not hard-code the source path against `node_modules/` or any other convention.",
|
|
15
|
+
"Write one workflow file per workflow in the set to the provider's workflow path (v2.0.0: `.github/workflows/` for GHA); alternate providers translate per the four-point mapping doc.",
|
|
16
|
+
"Substitute the trigger branch name into the produced commit-triggered workflows: on `branchModel: feature`, the `pull-request-checks` and `default-branch-checks` triggers name `workflowShape.defaultBranch` (default `main`); on `branchModel: trunk`, they name `workflowShape.trunkBranch` (default `main`). The substitution is applied against the `# @@RCF-SUB-BRANCH-NAME@@` marker that the illustrative assets carry immediately above their `branches:` line; the marker's next line (the `branches: [...]` list) is rewritten to name the configured branch, so the property AC-6114/6115 binds to (the branch model determines which branch triggers the workflow) is observable in the materialised output rather than being masked by a hard-coded `main`.",
|
|
17
|
+
"Substitute the package-manager setup and install steps into every produced workflow: read `workflowShape.packageManager` (default `pnpm`; recognised values `pnpm`, `npm`, `yarn`, `bun`); replace every line fenced between `# @@RCF-SUB-PKG-MGR-SETUP-BEGIN@@` and `# @@RCF-SUB-PKG-MGR-SETUP-END@@` (exclusive) with the per-manager setup block, and every line fenced between `# @@RCF-SUB-PKG-MGR-INSTALL-BEGIN@@` and `# @@RCF-SUB-PKG-MGR-INSTALL-END@@` (exclusive) with the per-manager install block, per the substitution table in `guide/delivery-ci-workflows.md`. Refuse an unrecognised `packageManager` with the same boot-check error path the enumerated fields use.",
|
|
15
18
|
"Materialisation is idempotent: re-running with the same `workflowShape` writes the same files byte-for-byte; changing the shape overwrites the workflow files atomically (temp file + rename).",
|
|
16
|
-
"Emit a materialiser-run summary to stdout naming the workflow files produced
|
|
19
|
+
"Emit a materialiser-run summary to stdout naming the workflow files produced, the elicited-check set they carry, and the substituted branch name and package manager."
|
|
17
20
|
],
|
|
18
21
|
"internalStructure": "One Node module exporting `materialise({ configPath, providerAssetDir, workflowOutDir })` and a shebang entry point invocable as `node <path>` with no required argv. The reader parses the config; the boot-check module validates each field against the recognised enumeration; the matrix module computes the workflow set from the composed shape; the writer module reads the provider asset templates, substitutes the required check set into the gate-runner invocation line, and writes the workflow files atomically.",
|
|
19
22
|
"interfaces": [
|
|
@@ -25,7 +28,7 @@
|
|
|
25
28
|
{
|
|
26
29
|
"name": "workflow-shape",
|
|
27
30
|
"kind": "value",
|
|
28
|
-
"description": "The parsed and composed `workflowShape`: `{ branchModel, checkSet, releaseMode, providerHint, scheduledAudit, trunkPullRequests }` with alias expansion applied
|
|
31
|
+
"description": "The parsed and composed `workflowShape`: `{ branchModel, checkSet, releaseMode, providerHint, scheduledAudit, trunkPullRequests, defaultBranch, trunkBranch, packageManager }` with alias expansion applied, `releaseMode` defaulted to `none` when absent, `defaultBranch` and `trunkBranch` defaulted to `main` when absent, and `packageManager` defaulted to `pnpm` when absent."
|
|
29
32
|
},
|
|
30
33
|
{
|
|
31
34
|
"name": "entry-point CLI",
|
|
@@ -60,5 +63,5 @@
|
|
|
60
63
|
],
|
|
61
64
|
"tradeoffs": "A build-time materialiser costs an extra step in the project's onboarding (running `node <entryPointPath>` after editing the config) but buys byte-identical workflow files across two runs of the same shape, which is the property downstream tools (a diff-review, a CI-config-verifier) depend on. Writing to the provider workflow path (not a `dist/` directory) means the workflow files are committed and reviewable; the shape declaration and the materialised output are both under source control.",
|
|
62
65
|
"createdAt": "2026-08-31T00:00:00Z",
|
|
63
|
-
"updatedAt": "2026-08-
|
|
66
|
+
"updatedAt": "2026-08-31T12:00:00Z"
|
|
64
67
|
}
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"id": "AC-6111-2",
|
|
23
|
-
"description": "The three required fields (`branchModel`, `checkSet`, `providerHint`), the optional `releaseMode` field (absent has the same effect as `releaseMode: none`, per Q6-B ratification), the optional `scheduledAudit`
|
|
23
|
+
"description": "The three required fields (`branchModel`, `checkSet`, `providerHint`), the optional `releaseMode` field (absent has the same effect as `releaseMode: none`, per Q6-B ratification), the optional `scheduledAudit` field, the optional `trunkPullRequests` sub-field, and the optional substitution-surface fields `defaultBranch` (default `main`), `trunkBranch` (default `main`), and `packageManager` (default `pnpm`; recognised set `pnpm`, `npm`, `yarn`, `bun`) are the complete surface the workflow-materialiser reads. No other field the file carries is interpreted at this version; unknown fields are ignored (forward-compatibility).",
|
|
24
24
|
"given": "a `workflowShape` declaration carrying the three required fields, the optional fields, and additional unknown fields",
|
|
25
25
|
"when": "the workflow-materialiser reads the file",
|
|
26
|
-
"then": "the materialiser processes the recognised fields and ignores the unknown fields without failing the boot-check",
|
|
26
|
+
"then": "the materialiser processes the recognised fields (including the substitution-surface trio) and ignores the unknown fields without failing the boot-check",
|
|
27
27
|
"testable": true,
|
|
28
28
|
"scope": "runtime"
|
|
29
29
|
}
|
|
@@ -11,19 +11,19 @@
|
|
|
11
11
|
"acceptanceCriteria": [
|
|
12
12
|
{
|
|
13
13
|
"id": "AC-6114-1",
|
|
14
|
-
"description": "With `workflowShape.branchModel: feature`, the materialiser produces a `pull-request-checks` workflow triggered by pull requests targeting the default branch. The workflow invokes the Node gate-runner entry point (TAC-701) with the required check set (mandatory tier plus every `checkSet` flag set true).",
|
|
15
|
-
"given": "a `workflowShape` with `branchModel: feature` and every `checkSet` flag true",
|
|
14
|
+
"description": "With `workflowShape.branchModel: feature`, the materialiser produces a `pull-request-checks` workflow triggered by pull requests targeting the configured default branch (`workflowShape.defaultBranch`, default `main`). The workflow invokes the Node gate-runner entry point (TAC-701) with the required check set (mandatory tier plus every `checkSet` flag set true).",
|
|
15
|
+
"given": "a `workflowShape` with `branchModel: feature`, `defaultBranch: develop`, and every `checkSet` flag true",
|
|
16
16
|
"when": "the workflow-materialiser runs",
|
|
17
|
-
"then": "a `pull-request-checks` workflow file lands at the provider's workflow path, its trigger
|
|
17
|
+
"then": "a `pull-request-checks` workflow file lands at the provider's workflow path, its trigger's branch list names `develop` (the configured default branch, not the placeholder marker and not a hard-coded `main`), and its command line invokes the Node gate-runner entry point with the required check set",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"id": "AC-6114-2",
|
|
23
|
-
"description": "With `branchModel: feature`, the materialiser additionally produces a `default-branch-checks` workflow triggered by pushes to the default branch. The workflow invokes the same Node gate-runner entry point on the same required check set.",
|
|
24
|
-
"given": "the same `workflowShape`",
|
|
23
|
+
"description": "With `branchModel: feature`, the materialiser additionally produces a `default-branch-checks` workflow triggered by pushes to the configured default branch. The workflow invokes the same Node gate-runner entry point on the same required check set.",
|
|
24
|
+
"given": "the same `workflowShape` (`branchModel: feature`, `defaultBranch: develop`)",
|
|
25
25
|
"when": "the workflow-materialiser runs",
|
|
26
|
-
"then": "a `default-branch-checks` workflow file lands at the provider's workflow path, its trigger
|
|
26
|
+
"then": "a `default-branch-checks` workflow file lands at the provider's workflow path, its trigger's branch list names `develop`, and its command line invokes the Node gate-runner entry point with the required check set",
|
|
27
27
|
"testable": true,
|
|
28
28
|
"scope": "runtime"
|
|
29
29
|
},
|
|
@@ -11,19 +11,19 @@
|
|
|
11
11
|
"acceptanceCriteria": [
|
|
12
12
|
{
|
|
13
13
|
"id": "AC-6115-1",
|
|
14
|
-
"description": "With `workflowShape.branchModel: trunk` and `trunkPullRequests` absent or `never`, the materialiser produces `default-branch-checks` only. The `pull-request-checks` workflow does not ship in this shape.",
|
|
15
|
-
"given": "a `workflowShape` with `branchModel: trunk` and no `trunkPullRequests` field",
|
|
14
|
+
"description": "With `workflowShape.branchModel: trunk` and `trunkPullRequests` absent or `never`, the materialiser produces `default-branch-checks` only, triggered by pushes to the configured trunk branch (`workflowShape.trunkBranch`, default `main`). The `pull-request-checks` workflow does not ship in this shape.",
|
|
15
|
+
"given": "a `workflowShape` with `branchModel: trunk`, `trunkBranch: trunk`, and no `trunkPullRequests` field",
|
|
16
16
|
"when": "the workflow-materialiser runs",
|
|
17
|
-
"then": "a `default-branch-checks` workflow file lands at the provider's workflow path, no `pull-request-checks` workflow file lands, and the merge-policy AC binds to the trunk-branch push-protection property",
|
|
17
|
+
"then": "a `default-branch-checks` workflow file lands at the provider's workflow path, its trigger's branch list names `trunk` (the configured trunk branch), no `pull-request-checks` workflow file lands, and the merge-policy AC binds to the trunk-branch push-protection property",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"id": "AC-6115-2",
|
|
23
|
-
"description": "With `branchModel: trunk` and `trunkPullRequests: sometimes`, the materialiser additionally produces `pull-request-checks` on the same required check set as `default-branch-checks
|
|
24
|
-
"given": "a `workflowShape` with `branchModel: trunk` and `trunkPullRequests: sometimes`",
|
|
23
|
+
"description": "With `branchModel: trunk` and `trunkPullRequests: sometimes`, the materialiser additionally produces `pull-request-checks` on the same required check set as `default-branch-checks`, both triggered against the configured trunk branch.",
|
|
24
|
+
"given": "a `workflowShape` with `branchModel: trunk`, `trunkBranch: trunk`, and `trunkPullRequests: sometimes`",
|
|
25
25
|
"when": "the workflow-materialiser runs",
|
|
26
|
-
"then": "both workflow files land at the provider's workflow path
|
|
26
|
+
"then": "both workflow files land at the provider's workflow path with their trigger branch lists naming `trunk`, and the merge-policy AC binds to the trunk-branch protection property that admits only PRs whose `pull-request-checks` aggregate passed",
|
|
27
27
|
"testable": true,
|
|
28
28
|
"scope": "runtime"
|
|
29
29
|
}
|
|
@@ -67,7 +67,9 @@ The project ships `.rcf/config/delivery-ci-workflows.json` at the project root.
|
|
|
67
67
|
},
|
|
68
68
|
"releaseMode": "tagPlusArtefact",
|
|
69
69
|
"providerHint": "githubActions",
|
|
70
|
-
"scheduledAudit": false
|
|
70
|
+
"scheduledAudit": false,
|
|
71
|
+
"defaultBranch": "main",
|
|
72
|
+
"packageManager": "pnpm"
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
```
|
|
@@ -78,11 +80,25 @@ The project ships `.rcf/config/delivery-ci-workflows.json` at the project root.
|
|
|
78
80
|
- `releaseMode` (OPTIONAL, Q6-B ratification): `none`, `tagOnly`, `tagPlusArtefact`, or `deployHandoff:<slug>`. An absent field is treated the same as `none` (no release workflow ships). Not having a release path yet is a common scenario when starting a project; the ratification chose ergonomics over an explicit `none`.
|
|
79
81
|
- `scheduledAudit` (optional): boolean; default `false`.
|
|
80
82
|
- `trunkPullRequests` (optional; qualifies `branchModel: trunk`): `never` or `sometimes`; default `never`.
|
|
83
|
+
- `defaultBranch` (optional; qualifies `branchModel: feature`): the branch name the commit-triggered workflows fire against; default `main`. The materialiser substitutes this value into the trigger's branch list; a project on `develop`, `master`, or any other convention names it here rather than hand-editing the workflow files.
|
|
84
|
+
- `trunkBranch` (optional; qualifies `branchModel: trunk`): the trunk branch name the commit-triggered workflows fire against; default `main`. Same substitution semantics as `defaultBranch`.
|
|
85
|
+
- `packageManager` (optional): `pnpm`, `npm`, `yarn`, or `bun`; default `pnpm`. Selects the within-provider package-manager substitution the materialiser applies to every produced workflow (see the substitution table below).
|
|
81
86
|
|
|
82
87
|
### Profile aliases
|
|
83
88
|
|
|
84
89
|
Named profile aliases (`smallLibrary`, `smallService`, `internalPackage`, `trunkLibrary`) compose the four-field form. The materialiser expands the alias to the four-field form at boot; every AC binds to the four-field form. Adding an alias is a minor bump; renaming or removing an alias is a minor bump.
|
|
85
90
|
|
|
91
|
+
## Bootstrap posture (the guaranteed-red first run)
|
|
92
|
+
|
|
93
|
+
A fresh apply of this blueprint alongside its usual companions (an `application-*` blueprint plus a `persistence-*` blueprint, for example) contributes tens to hundreds of REQs and ACs and zero test cases. The mandatory tier's `coverage-strict` gate runs `rcf audit coverage --strict` which refuses when any AC lacks a resolving TC (ADR-702). That means the very first commit-triggered workflow the materialiser produces is guaranteed to refuse until either every AC has a TC OR a project-level ADR demotes the gate. This is a cost the operator either accepts up-front or defers behind a stated exit criterion; either way, name the posture at apply time rather than discovering it when the first PR is refused.
|
|
94
|
+
|
|
95
|
+
Two ratified bootstrap postures:
|
|
96
|
+
|
|
97
|
+
- **Author every TC before turning CI green.** Every AC the blueprint set contributed gets a TC before the first push; the coverage-strict gate stays merge-blocking from day one. Right when the AC count is small or the project's engineering standards refuse any advisory-only CI window.
|
|
98
|
+
- **Demote coverage-strict to advisory-only for a bounded window.** Ship a project-level ADR that supersedes ADR-702 on the `strictCoverageGate` topic for the project only, demoting the gate to advisory (its per-gate report still lands; its `failed` outcome no longer flips the aggregate). Bind the ADR to a stated exit criterion (recommended: `N` consecutive `passed` outcomes on the default branch, e.g. `N = 5`, or an audit confirming every AC has a resolving TC, whichever comes first). Retire the ADR by flipping its status to `superseded` when the criterion is met; the coverage-strict gate reverts to merge-blocking without further action.
|
|
99
|
+
|
|
100
|
+
The blueprint ships a starting-point ADR for the demotion posture at `assets/bootstrap/adr-bootstrap-coverage-supersession.template.json` (see the sibling `README.md` for the copy-adapt-register steps). Copy it, rename the id, fill the timestamps, register it as `scope: global` on topic `strictCoverageGate`, and the CI's first run turns green on every non-coverage gate on day one. The debt is real; the exit criterion keeps it visible.
|
|
101
|
+
|
|
86
102
|
## The check catalogue
|
|
87
103
|
|
|
88
104
|
Two tiers:
|
|
@@ -118,6 +134,28 @@ Every mainstream CI provider ships the same four ingredients under a different r
|
|
|
118
134
|
|
|
119
135
|
See `assets/ci-provider-examples/notes.md` for the per-provider translation.
|
|
120
136
|
|
|
137
|
+
## Within-provider package-manager substitution
|
|
138
|
+
|
|
139
|
+
The four-point mapping covers translation across providers; a project stays on one provider and picks its package manager. The illustrative GHA assets carry paired substitution markers fencing the setup step(s) and the install step; the materialiser replaces every line between the two fence markers (exclusive) with the per-manager block. The markers are YAML comments so the illustrative file is valid YAML on its own and reviewable as a diff after materialisation:
|
|
140
|
+
|
|
141
|
+
- `# @@RCF-SUB-PKG-MGR-SETUP-BEGIN@@` and `# @@RCF-SUB-PKG-MGR-SETUP-END@@` fence the manager's setup step(s).
|
|
142
|
+
- `# @@RCF-SUB-PKG-MGR-INSTALL-BEGIN@@` and `# @@RCF-SUB-PKG-MGR-INSTALL-END@@` fence the manager's install step.
|
|
143
|
+
|
|
144
|
+
The four recognised managers map to these blocks (GHA reference; alternate providers use the equivalent action or command in the provider's own runner language):
|
|
145
|
+
|
|
146
|
+
| `packageManager` | setup step | install line |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `pnpm` (default) | `- uses: pnpm/action-setup@v4` followed by `with: { version: 9 }`; then `- uses: actions/setup-node@v4` with `node-version: 24` and `cache: pnpm` | `pnpm install --frozen-lockfile` |
|
|
149
|
+
| `npm` | `- uses: actions/setup-node@v4` with `node-version: 24` and `cache: npm` | `npm ci` |
|
|
150
|
+
| `yarn` | `- uses: actions/setup-node@v4` with `node-version: 24` and `cache: yarn` (yarn v1 or `enable-corepack` for berry) | `yarn install --frozen-lockfile` (v1) or `yarn install --immutable` (berry) |
|
|
151
|
+
| `bun` | `- uses: oven-sh/setup-bun@v2` with `bun-version: latest` | `bun install --frozen-lockfile` |
|
|
152
|
+
|
|
153
|
+
An unrecognised `packageManager` value refuses at the same boot-check exit path the enumerated fields use (AC-6112-2 shape).
|
|
154
|
+
|
|
155
|
+
## Trigger branch-name substitution
|
|
156
|
+
|
|
157
|
+
The commit-triggered assets (`pull-request-checks.yml`, `default-branch-checks.yml`) carry `# @@RCF-SUB-BRANCH-NAME@@` on the line immediately above the `branches:` list. The materialiser rewrites that one `branches:` line from `workflowShape.defaultBranch` (feature model) or `workflowShape.trunkBranch` (trunk model) so the branch-model AC (AC-6114/6115) is observable in the materialised output rather than being masked by a hard-coded `main`. Both fields default to `main`, so a project on the default keeps the current behaviour without touching the fields.
|
|
158
|
+
|
|
121
159
|
## Operator decisions that remain open after apply
|
|
122
160
|
|
|
123
161
|
- The workflow shape declaration (`.rcf/config/delivery-ci-workflows.json` populated).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rcf-lite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "One-install tooling for the Requirements Confidence Framework (RCF): the unified `rcf` CLI grouped into the five RCF tool groups (discover, define, build, verify, audit) plus a small core set (init, doctor, guidance, mcp), an MCP server, the live tree viewer and the fresh-context adversarial ship-gate verifier. Consumes @stravica-ai/rcf-schemas.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,18 +39,6 @@
|
|
|
39
39
|
"registry": "https://registry.npmjs.org/",
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
|
-
"scripts": {
|
|
43
|
-
"test": "node --test --test-concurrency=1 'test/**/*.test.js'",
|
|
44
|
-
"vendor": "node scripts/vendor-mermaid.mjs",
|
|
45
|
-
"build:managed": "node scripts/gen-managed-artefacts.mjs",
|
|
46
|
-
"canary:register": "node scripts/canary-register.mjs",
|
|
47
|
-
"validate:releases": "node scripts/validate-releases.mjs",
|
|
48
|
-
"stage:blueprints": "node scripts/stage-blueprint-shelf.mjs",
|
|
49
|
-
"preinstall": "node scripts/preinstall-node-check.mjs",
|
|
50
|
-
"prepack": "node scripts/stage-blueprint-shelf.mjs",
|
|
51
|
-
"prepublishOnly": "node scripts/gen-managed-artefacts.mjs && node scripts/stage-blueprint-shelf.mjs && node scripts/validate-releases.mjs",
|
|
52
|
-
"rcf": "node bin/rcf.js"
|
|
53
|
-
},
|
|
54
42
|
"repository": {
|
|
55
43
|
"type": "git",
|
|
56
44
|
"url": "git+https://github.com/Stravica/rcf-lite.git",
|
|
@@ -72,7 +60,7 @@
|
|
|
72
60
|
"#admissibility": "./src/admissibility/index.js"
|
|
73
61
|
},
|
|
74
62
|
"dependencies": {
|
|
75
|
-
"@stravica-ai/rcf-schemas": "0.5.
|
|
63
|
+
"@stravica-ai/rcf-schemas": "0.5.1",
|
|
76
64
|
"ajv": "^8.20.0",
|
|
77
65
|
"ajv-formats": "^3.0.1"
|
|
78
66
|
},
|
|
@@ -80,5 +68,15 @@
|
|
|
80
68
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
81
69
|
"js-yaml": "^4.1.0",
|
|
82
70
|
"mermaid": "11.6.0"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"test": "node --test --test-concurrency=1 'test/**/*.test.js'",
|
|
74
|
+
"vendor": "node scripts/vendor-mermaid.mjs",
|
|
75
|
+
"build:managed": "node scripts/gen-managed-artefacts.mjs",
|
|
76
|
+
"canary:register": "node scripts/canary-register.mjs",
|
|
77
|
+
"validate:releases": "node scripts/validate-releases.mjs",
|
|
78
|
+
"stage:blueprints": "node scripts/stage-blueprint-shelf.mjs",
|
|
79
|
+
"preinstall": "node scripts/preinstall-node-check.mjs",
|
|
80
|
+
"rcf": "node bin/rcf.js"
|
|
83
81
|
}
|
|
84
|
-
}
|
|
82
|
+
}
|
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.15.0"
|
|
44
44
|
releases:
|
|
45
|
+
- version: "0.15.0"
|
|
46
|
+
date: "2026-08-31"
|
|
47
|
+
breaking: false
|
|
48
|
+
headlines:
|
|
49
|
+
- "Library-applied blueprints now carry a durable ownership stamp so re-registering a library under a different prefix no longer orphans records that came from it."
|
|
50
|
+
- "The review-on-add card gained a global-topics section and a prefix-check line so a library's cross-topic surface is visible before the operator commits."
|
|
51
|
+
- "The delivery-ci-workflows blueprint gained optional package-manager and branch-name shape fields, taught the coverage-strict bootstrap trap in the guide, and shipped a starting-point supersession ADR."
|
|
52
|
+
minAgentAction: null
|
|
53
|
+
notesUrl: "https://stravica.ai/docs/rcf/changelog/"
|
|
54
|
+
|
|
45
55
|
- version: "0.14.0"
|
|
46
56
|
date: "2026-08-31"
|
|
47
57
|
breaking: true
|
package/src/blueprint/apply.js
CHANGED
|
@@ -49,6 +49,13 @@ import { nextResolutionId } from './resolutions.js';
|
|
|
49
49
|
* record, so `rcf define blueprint remove wsd-auth-oauth2` reads back
|
|
50
50
|
* cleanly. `namespaceOverride` still wins over `effectiveSlug` if both
|
|
51
51
|
* are set (operator explicitly chose a different namespace).
|
|
52
|
+
* @param {string} [args.libraryPrefix] - the registered library prefix the
|
|
53
|
+
* blueprint was resolved through (spec §5.3, §7.3). When set the applied
|
|
54
|
+
* record carries a `libraryPrefix` field so `rcf library remove`'s
|
|
55
|
+
* ownership check reads the ownership fact off the record itself
|
|
56
|
+
* rather than string-matching `source`. Absent for shelf and path
|
|
57
|
+
* applies. Requires @stravica-ai/rcf-schemas 0.5.1 or later
|
|
58
|
+
* (`appliedBlueprintRecord.libraryPrefix`, additive optional).
|
|
52
59
|
* @param {{ ac: { start: number, end: number }, suffixBlocks?: Array<{ kind: string, start: number, end: number }> }} [args.libraryBands]
|
|
53
60
|
* Declared bands from the resolved library. When set, every stamped
|
|
54
61
|
* contribution is band-gated before write; a contribution whose numeric
|
|
@@ -72,7 +79,7 @@ import { nextResolutionId } from './resolutions.js';
|
|
|
72
79
|
* prove the rollback runs. Never used in production.
|
|
73
80
|
* @returns {Promise<ApplyResult | import('../core/errors/index.js').RcfError>}
|
|
74
81
|
*/
|
|
75
|
-
export async function applyBlueprint({ projectRoot, tree, source, displaySource, namespaceOverride, effectiveSlug, libraryBands, resolveDeclarations, now = new Date(), dryRun = false, _copyFileForTest }) {
|
|
82
|
+
export async function applyBlueprint({ projectRoot, tree, source, displaySource, namespaceOverride, effectiveSlug, libraryPrefix, libraryBands, resolveDeclarations, now = new Date(), dryRun = false, _copyFileForTest }) {
|
|
76
83
|
const hintSource = typeof displaySource === 'string' && displaySource.length > 0 ? displaySource : source;
|
|
77
84
|
const blueprint = await loadBlueprint(source);
|
|
78
85
|
if (blueprint.kind) return blueprint; // RcfError
|
|
@@ -256,11 +263,12 @@ export async function applyBlueprint({ projectRoot, tree, source, displaySource,
|
|
|
256
263
|
// carries the qualified typed ref for library-resolved blueprints
|
|
257
264
|
// (`wsd:auth-oauth2`) so `rcf define blueprint upgrade` reads back
|
|
258
265
|
// cleanly (spec §5.3); local-path applies carry the absolute path as
|
|
259
|
-
// today.
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
266
|
+
// today. When the apply resolved through a registered external
|
|
267
|
+
// library the record additionally carries `libraryPrefix`: the
|
|
268
|
+
// ownership fact for the library-registered ownership check in
|
|
269
|
+
// `rcf library remove`, so the registry may be edited (renamed,
|
|
270
|
+
// re-pointed, unregistered) without orphaning previously applied
|
|
271
|
+
// records. Shelf and path applies carry no `libraryPrefix`.
|
|
264
272
|
const recordSource = typeof displaySource === 'string' && displaySource.length > 0 && displaySource !== source
|
|
265
273
|
? displaySource
|
|
266
274
|
: source;
|
|
@@ -270,6 +278,7 @@ export async function applyBlueprint({ projectRoot, tree, source, displaySource,
|
|
|
270
278
|
appliedAt: now.toISOString(),
|
|
271
279
|
source: recordSource,
|
|
272
280
|
...(namespaceOverride ? { namespace: namespaceOverride } : {}),
|
|
281
|
+
...(typeof libraryPrefix === 'string' && libraryPrefix.length > 0 ? { libraryPrefix } : {}),
|
|
273
282
|
...(writtenContributions.length > 0 ? { contributions: writtenContributions } : {}),
|
|
274
283
|
};
|
|
275
284
|
const manifestResult = await updateManifest({
|
|
@@ -44,6 +44,12 @@ const LIBRARY_VERSION_KNOWN = 1;
|
|
|
44
44
|
* @typedef {object} LibraryBlueprintEntry
|
|
45
45
|
* @property {string} slug
|
|
46
46
|
* @property {string} path
|
|
47
|
+
* @property {string[]} [globalTopics] scope:global ADR topics this
|
|
48
|
+
* blueprint contributes. Populated only when the loader ran with
|
|
49
|
+
* `validateBlueprints: true` (the review-on-add path); resolver-time
|
|
50
|
+
* loads that skip per-blueprint validation leave the field absent.
|
|
51
|
+
* Callers use it to render the section 8.1 "Global topics these
|
|
52
|
+
* blueprints claim" line during library-add review.
|
|
47
53
|
*/
|
|
48
54
|
|
|
49
55
|
/**
|
|
@@ -266,6 +272,21 @@ async function validateDeclaredBlueprints(library) {
|
|
|
266
272
|
filePath: bpRoot,
|
|
267
273
|
});
|
|
268
274
|
}
|
|
275
|
+
// Attach the scope:global ADR topics this blueprint claims so the
|
|
276
|
+
// review-on-add printer can render the spec §8.1 "Global topics
|
|
277
|
+
// these blueprints claim" line without re-walking every blueprint.
|
|
278
|
+
// Order is contribution-declaration order; duplicates within one
|
|
279
|
+
// blueprint (an author mistake caught by phase-1 conflict logic)
|
|
280
|
+
// are de-duplicated here to keep the render terse.
|
|
281
|
+
const seen = new Set();
|
|
282
|
+
const topics = [];
|
|
283
|
+
for (const c of loaded.contributions ?? []) {
|
|
284
|
+
if (c.scope === 'global' && typeof c.topic === 'string' && !seen.has(c.topic)) {
|
|
285
|
+
seen.add(c.topic);
|
|
286
|
+
topics.push(c.topic);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
entry.globalTopics = topics;
|
|
269
290
|
}
|
|
270
291
|
return null;
|
|
271
292
|
}
|
|
@@ -14,13 +14,20 @@
|
|
|
14
14
|
// This is what makes option 3 as printed by the reshaped conflict
|
|
15
15
|
// message executable VERBATIM from the refused-add state: the operator
|
|
16
16
|
// runs `rcf define blueprint supersede <topic> --incoming <source>` immediately
|
|
17
|
-
// after the refused add, with zero prep; the verb
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
17
|
+
// after the refused add, with zero prep; the verb resolves the incoming
|
|
18
|
+
// source through `resolveBlueprintSource` (the SAME resolver `add`
|
|
19
|
+
// uses, so `@stock/<slug>`, bare kebab slugs, colon-qualified library
|
|
20
|
+
// refs, and filesystem paths are all accepted here just as they are on
|
|
21
|
+
// `add`), loads the incoming blueprint from disk, finds its
|
|
22
|
+
// scope:global ADR on <topic>, stamps the id into the incoming
|
|
23
|
+
// blueprint's namespace, and uses that {slug, adrId} as the second
|
|
24
|
+
// side of supersedes[]. Round-2 shipped with the writer requiring 2
|
|
25
|
+
// already-applied ADRs, which meant option 3 as printed exited 2 in
|
|
26
|
+
// the refused-add state; the escalation the worker adapted around was
|
|
27
|
+
// that AC-1002-5 could not pass as written. The `--incoming` argument
|
|
28
|
+
// was subsequently fed directly to `loadBlueprint`, which understands
|
|
29
|
+
// paths only — a newcomer who intuited the `@stock/<slug>` form from
|
|
30
|
+
// `add`'s help hit a refusal. Persona re-run 2026-08-31 arc-4, H2.
|
|
24
31
|
//
|
|
25
32
|
// Two side effects, both governed by dryRun:
|
|
26
33
|
// 1. Writes the project ADR file (JSON, minimally valid — the operator
|
|
@@ -46,11 +53,12 @@
|
|
|
46
53
|
import { mkdir, rename, stat, unlink, writeFile } from 'node:fs/promises';
|
|
47
54
|
import { dirname, join } from 'node:path';
|
|
48
55
|
|
|
49
|
-
import { rcfError } from '../core/errors/index.js';
|
|
56
|
+
import { isRcfError, rcfError } from '../core/errors/index.js';
|
|
50
57
|
import { loadBlueprint } from './loader.js';
|
|
51
58
|
import { updateManifest } from './manifest-writer.js';
|
|
52
59
|
import { stampId } from './namespace.js';
|
|
53
60
|
import { nextResolutionId } from './resolutions.js';
|
|
61
|
+
import { resolveBlueprintSource } from './shelf-resolver.js';
|
|
54
62
|
|
|
55
63
|
/**
|
|
56
64
|
* @typedef {object} SupersedeResult
|
|
@@ -79,9 +87,16 @@ import { nextResolutionId } from './resolutions.js';
|
|
|
79
87
|
* @param {Date} [args.now]
|
|
80
88
|
* @param {boolean} [args.dryRun]
|
|
81
89
|
* @param {string} [args.reason]
|
|
90
|
+
* @param {(source: string, opts: { projectRoot: string }) => Promise<import('./shelf-resolver.js').ResolvedSource | import('../core/errors/index.js').RcfError>} [args._resolveSource]
|
|
91
|
+
* Test-only injection point; production callers omit this and the
|
|
92
|
+
* shared `resolveBlueprintSource` runs. Mirrors the same DI on
|
|
93
|
+
* `view/scope.js` so a hermetic test can exercise the `@stock/` and
|
|
94
|
+
* colon-qualified branches without staging the packaged shelf or a
|
|
95
|
+
* library registry.
|
|
82
96
|
* @returns {Promise<SupersedeResult | import('../core/errors/index.js').RcfError>}
|
|
83
97
|
*/
|
|
84
|
-
export async function supersedeBlueprintTopic({ projectRoot, tree, topic, incomingSource, now = new Date(), dryRun = false, reason }) {
|
|
98
|
+
export async function supersedeBlueprintTopic({ projectRoot, tree, topic, incomingSource, now = new Date(), dryRun = false, reason, _resolveSource }) {
|
|
99
|
+
const resolveImpl = _resolveSource ?? resolveBlueprintSource;
|
|
85
100
|
if (typeof topic !== 'string' || topic.trim().length === 0) {
|
|
86
101
|
// Schema minLength:1 accepts whitespace-only; the writer refuses
|
|
87
102
|
// it up-front so a whitespace-only topic never lands on disk.
|
|
@@ -122,8 +137,27 @@ export async function supersedeBlueprintTopic({ projectRoot, tree, topic, incomi
|
|
|
122
137
|
// incomingSource is informational only (skipped silently unless it
|
|
123
138
|
// would add a distinct {slug, adrId} pair, in which case it is
|
|
124
139
|
// appended for a >= 3-blueprint scenario).
|
|
140
|
+
//
|
|
141
|
+
// The incoming source is routed through the SAME resolver `add` uses
|
|
142
|
+
// (`resolveBlueprintSource`), so `@stock/<slug>`, bare kebab slugs,
|
|
143
|
+
// colon-qualified library refs, and filesystem paths are all accepted
|
|
144
|
+
// here just as they are on `add`. Before this routing the verb fed
|
|
145
|
+
// the raw string directly to `loadBlueprint`, which understands paths
|
|
146
|
+
// only; a newcomer following the conflict card's own printed remedy
|
|
147
|
+
// with the ergonomic `@stock/<slug>` form (identical to what `add`
|
|
148
|
+
// accepted) hit a refusal. Persona re-run 2026-08-31 arc-4, H2.
|
|
125
149
|
if (typeof incomingSource === 'string' && incomingSource.length > 0) {
|
|
126
|
-
const
|
|
150
|
+
const resolved = await resolveImpl(incomingSource, { projectRoot });
|
|
151
|
+
if (isRcfError(resolved)) {
|
|
152
|
+
// Wrap the resolver's error under the --incoming banner so the
|
|
153
|
+
// operator sees which flag failed.
|
|
154
|
+
return rcfError({
|
|
155
|
+
kind: resolved.kind,
|
|
156
|
+
message: `--incoming ${incomingSource}: ${resolved.message}`,
|
|
157
|
+
filePath: resolved.filePath,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const loaded = await loadBlueprint(resolved.resolved);
|
|
127
161
|
if (loaded.kind) {
|
|
128
162
|
// Preserve the loader's own rcfError but drop any 'blueprint: '
|
|
129
163
|
// narrator prefix so the CLI's `[error] blueprint supersede: `
|
|
@@ -134,6 +168,15 @@ export async function supersedeBlueprintTopic({ projectRoot, tree, topic, incomi
|
|
|
134
168
|
filePath: loaded.filePath,
|
|
135
169
|
});
|
|
136
170
|
}
|
|
171
|
+
// For library-qualified sources the applied identity is rewired
|
|
172
|
+
// under the library prefix (`<libraryPrefix>-<blueprintSlug>`), so
|
|
173
|
+
// the supersedes[] entry must reference that effective slug and
|
|
174
|
+
// stamp the ADR id under it — matching what `apply.js` writes to
|
|
175
|
+
// `manifest.blueprints[].slug`. For shelf / path sources the
|
|
176
|
+
// blueprint's own slug applies.
|
|
177
|
+
const effectiveSlug = resolved.kind === 'library'
|
|
178
|
+
? resolved.effectiveSlug
|
|
179
|
+
: loaded.slug;
|
|
137
180
|
let matched = null;
|
|
138
181
|
for (const c of loaded.contributions ?? []) {
|
|
139
182
|
if (c.kind === 'adr' && c.scope === 'global' && c.topic === topic) {
|
|
@@ -144,14 +187,14 @@ export async function supersedeBlueprintTopic({ projectRoot, tree, topic, incomi
|
|
|
144
187
|
if (!matched) {
|
|
145
188
|
return rcfError({
|
|
146
189
|
kind: 'usage',
|
|
147
|
-
message: `--incoming ${incomingSource}: blueprint '${
|
|
190
|
+
message: `--incoming ${incomingSource}: blueprint '${effectiveSlug}' declares no scope:global ADR on topic '${topic}'.`,
|
|
148
191
|
});
|
|
149
192
|
}
|
|
150
|
-
const stamped = stampId(matched.id,
|
|
193
|
+
const stamped = stampId(matched.id, effectiveSlug);
|
|
151
194
|
if ('error' in stamped) {
|
|
152
195
|
return rcfError({ kind: 'validation', message: `--incoming ${incomingSource}: ${stamped.error}` });
|
|
153
196
|
}
|
|
154
|
-
const incomingPair = { slug:
|
|
197
|
+
const incomingPair = { slug: effectiveSlug, adrId: stamped.id, path: `rcf/adrs/${stamped.id.toLowerCase()}.json` };
|
|
155
198
|
// Dedupe against the applied side: an incoming blueprint that is
|
|
156
199
|
// also currently applied (unusual — refused-add state means it is
|
|
157
200
|
// NOT applied) would otherwise be double-listed.
|
|
@@ -313,13 +313,26 @@ async function handleRemove({ args, parsed, projectRoot, stdout, stderr }) {
|
|
|
313
313
|
return 2;
|
|
314
314
|
}
|
|
315
315
|
// Refuse when any applied blueprint on the project came through the
|
|
316
|
-
// library.
|
|
316
|
+
// library. The ownership fact lives on the record itself as
|
|
317
|
+
// `libraryPrefix` (stamped at apply-time when the apply resolved
|
|
318
|
+
// through this registry). Records applied before the field shipped
|
|
319
|
+
// (@stravica-ai/rcf-schemas 0.5.1) carry no `libraryPrefix`; for
|
|
320
|
+
// those we fall back to the pre-field signal, `source` starting with
|
|
321
|
+
// `<prefix>:`. Preferring the record over the string match makes the
|
|
322
|
+
// ownership durable across registry edits: a library re-registered
|
|
323
|
+
// under a different prefix does not orphan the records applied under
|
|
324
|
+
// the previous prefix.
|
|
317
325
|
const { tree, errors } = await walkTree({ projectRoot });
|
|
318
326
|
if (errors.length > 0) {
|
|
319
327
|
for (const e of errors) stderr.write(`[tree] ${e.kind}: ${e.message}\n`);
|
|
320
328
|
return 2;
|
|
321
329
|
}
|
|
322
|
-
const referring = (tree.manifest?.blueprints ?? []).filter((b) =>
|
|
330
|
+
const referring = (tree.manifest?.blueprints ?? []).filter((b) => {
|
|
331
|
+
if (typeof b.libraryPrefix === 'string' && b.libraryPrefix.length > 0) {
|
|
332
|
+
return b.libraryPrefix === libraryPrefix;
|
|
333
|
+
}
|
|
334
|
+
return typeof b.source === 'string' && b.source.startsWith(`${libraryPrefix}:`);
|
|
335
|
+
});
|
|
323
336
|
if (referring.length > 0) {
|
|
324
337
|
stderr.write(`[error] blueprint library remove: ${referring.length} applied blueprint(s) came through '${libraryPrefix}':\n`);
|
|
325
338
|
for (const r of referring) stderr.write(` ${r.slug} <- ${r.source}\n`);
|
|
@@ -403,8 +416,23 @@ function printReview({ stdout, ref, library, libraryPrefix, coreReservations })
|
|
|
403
416
|
for (const bp of library.blueprints) {
|
|
404
417
|
stdout.write(` ${libraryPrefix}:${bp.slug}\n`);
|
|
405
418
|
}
|
|
419
|
+
// Spec §8.1: surface the scope:global ADR topics each blueprint
|
|
420
|
+
// claims so the operator sees, at the review moment, which cross-
|
|
421
|
+
// library / cross-core trust-boundary interactions this add commits
|
|
422
|
+
// them to. Suppressed only when no blueprint on the library claims
|
|
423
|
+
// any global topic (the render would otherwise be a lonely header).
|
|
424
|
+
const withTopics = library.blueprints.filter((bp) => Array.isArray(bp.globalTopics) && bp.globalTopics.length > 0);
|
|
425
|
+
if (withTopics.length > 0) {
|
|
426
|
+
const qualifiedWidth = Math.max(...withTopics.map((bp) => `${libraryPrefix}:${bp.slug}`.length));
|
|
427
|
+
stdout.write(`\n Global topics these blueprints claim (may conflict with core or with other libraries):\n`);
|
|
428
|
+
for (const bp of withTopics) {
|
|
429
|
+
const qualified = `${libraryPrefix}:${bp.slug}`;
|
|
430
|
+
stdout.write(` ${qualified.padEnd(qualifiedWidth)} -> ${bp.globalTopics.join(', ')}\n`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
406
433
|
stdout.write(`\n Provenance : local (dev use)\n`);
|
|
407
434
|
stdout.write(` Band check : cross-checked ${coreReservations.ac.length} core AC row(s), ${coreReservations.suffixBlocks.length} core suffix block(s); no overlap.\n`);
|
|
435
|
+
stdout.write(` Prefix check : '${libraryPrefix}' does not collide with any core slug.\n`);
|
|
408
436
|
stdout.write(`\n`);
|
|
409
437
|
}
|
|
410
438
|
|
package/src/cli/blueprint.js
CHANGED
|
@@ -211,10 +211,14 @@ export async function main(argv, deps = {}) {
|
|
|
211
211
|
// the library prefix and forward the library's declared bands
|
|
212
212
|
// for the apply-time gate (spec §5.3, §8.3). The qualified typed
|
|
213
213
|
// ref is passed as `displaySource` so the applied record's
|
|
214
|
-
// `source` field carries it verbatim (spec §5.3).
|
|
214
|
+
// `source` field carries it verbatim (spec §5.3). The library
|
|
215
|
+
// prefix is stamped onto the applied record so the ownership
|
|
216
|
+
// fact for `rcf library remove` lives on the record itself
|
|
217
|
+
// rather than being re-derived by string-matching `source`.
|
|
215
218
|
...(resolved.kind === 'library' ? {
|
|
216
219
|
displaySource: resolved.original,
|
|
217
220
|
effectiveSlug: resolved.effectiveSlug,
|
|
221
|
+
libraryPrefix: resolved.libraryPrefix,
|
|
218
222
|
libraryBands: resolved.libraryBands,
|
|
219
223
|
} : {}),
|
|
220
224
|
resolveDeclarations: resolveDeclarations.value,
|