release-skill 0.9.32 → 0.9.34
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/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/plugin.json +1 -1
- package/.kimi-plugin/plugin.json +1 -1
- package/.qoder-plugin/plugin.json +1 -1
- package/CHANGELOG.md +54 -0
- package/INSTALL.md +2 -2
- package/INSTALL.zh-CN.md +2 -2
- package/README.md +31 -13
- package/README.zh-CN.md +17 -13
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +5855 -4045
- package/adapters/claude/schemas/.render-manifest.json +8 -4
- package/adapters/claude/schemas/release-plan.schema.json +85 -0
- package/adapters/claude/schemas/release-project.schema.json +18 -0
- package/adapters/claude/schemas/test-intake.schema.json +217 -0
- package/adapters/claude/skills/release-assess/SKILL.md +11 -0
- package/adapters/claude/skills/release-config/SKILL.md +6 -0
- package/adapters/claude/skills/release-help/SKILL.md +20 -4
- package/adapters/claude/skills/release-prepare/SKILL.md +11 -2
- package/adapters/claude/skills/release-setup/SKILL.md +15 -2
- package/adapters/claude/skills/release-verify/SKILL.md +2 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +5855 -4045
- package/adapters/codex/schemas/.render-manifest.json +8 -4
- package/adapters/codex/schemas/release-plan.schema.json +85 -0
- package/adapters/codex/schemas/release-project.schema.json +18 -0
- package/adapters/codex/schemas/test-intake.schema.json +217 -0
- package/adapters/codex/skills/release-assess/SKILL.md +11 -0
- package/adapters/codex/skills/release-config/SKILL.md +6 -0
- package/adapters/codex/skills/release-help/SKILL.md +20 -4
- package/adapters/codex/skills/release-prepare/SKILL.md +11 -2
- package/adapters/codex/skills/release-setup/SKILL.md +15 -2
- package/adapters/codex/skills/release-verify/SKILL.md +2 -0
- package/adapters/cursor/.cursor-plugin/plugin.json +1 -1
- package/adapters/cursor/bin/release-skill.bundle.mjs +5855 -4045
- package/adapters/cursor/schemas/.render-manifest.json +8 -4
- package/adapters/cursor/schemas/release-plan.schema.json +85 -0
- package/adapters/cursor/schemas/release-project.schema.json +18 -0
- package/adapters/cursor/schemas/test-intake.schema.json +217 -0
- package/adapters/cursor/skills/release-assess/SKILL.md +11 -0
- package/adapters/cursor/skills/release-config/SKILL.md +6 -0
- package/adapters/cursor/skills/release-help/SKILL.md +20 -4
- package/adapters/cursor/skills/release-prepare/SKILL.md +11 -2
- package/adapters/cursor/skills/release-setup/SKILL.md +15 -2
- package/adapters/cursor/skills/release-verify/SKILL.md +2 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +5855 -4045
- package/adapters/kimi/schemas/.render-manifest.json +8 -4
- package/adapters/kimi/schemas/release-plan.schema.json +85 -0
- package/adapters/kimi/schemas/release-project.schema.json +18 -0
- package/adapters/kimi/schemas/test-intake.schema.json +217 -0
- package/adapters/kimi/skills/release-assess/SKILL.md +11 -0
- package/adapters/kimi/skills/release-config/SKILL.md +6 -0
- package/adapters/kimi/skills/release-help/SKILL.md +20 -4
- package/adapters/kimi/skills/release-prepare/SKILL.md +11 -2
- package/adapters/kimi/skills/release-setup/SKILL.md +15 -2
- package/adapters/kimi/skills/release-verify/SKILL.md +2 -0
- package/adapters/qoder/.qoder-plugin/plugin.json +1 -1
- package/adapters/qoder/bin/release-skill.bundle.mjs +5855 -4045
- package/adapters/qoder/schemas/.render-manifest.json +8 -4
- package/adapters/qoder/schemas/release-plan.schema.json +85 -0
- package/adapters/qoder/schemas/release-project.schema.json +18 -0
- package/adapters/qoder/schemas/test-intake.schema.json +217 -0
- package/adapters/qoder/skills/release-assess/SKILL.md +11 -0
- package/adapters/qoder/skills/release-config/SKILL.md +6 -0
- package/adapters/qoder/skills/release-help/SKILL.md +20 -4
- package/adapters/qoder/skills/release-prepare/SKILL.md +11 -2
- package/adapters/qoder/skills/release-setup/SKILL.md +15 -2
- package/adapters/qoder/skills/release-verify/SKILL.md +2 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +5855 -4045
- package/adapters/workbuddy/schemas/.render-manifest.json +8 -4
- package/adapters/workbuddy/schemas/release-plan.schema.json +85 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +18 -0
- package/adapters/workbuddy/schemas/test-intake.schema.json +217 -0
- package/adapters/workbuddy/skills/release-assess/SKILL.md +11 -0
- package/adapters/workbuddy/skills/release-config/SKILL.md +6 -0
- package/adapters/workbuddy/skills/release-help/SKILL.md +20 -4
- package/adapters/workbuddy/skills/release-prepare/SKILL.md +11 -2
- package/adapters/workbuddy/skills/release-setup/SKILL.md +15 -2
- package/adapters/workbuddy/skills/release-verify/SKILL.md +2 -0
- package/bin/release-skill-cli.mjs +37 -2
- package/bin/release-skill.bundle.mjs +5855 -4045
- package/package.json +1 -1
- package/platform-manifest.json +4 -4
- package/references/.render-manifest.json +6 -6
- package/references/01-state-machine.md +12 -1
- package/references/02-project-config.md +20 -0
- package/references/05-evidence-and-errors.md +30 -0
- package/schemas/.render-manifest.json +8 -4
- package/schemas/release-plan.schema.json +85 -0
- package/schemas/release-project.schema.json +18 -0
- package/schemas/test-intake.schema.json +217 -0
- package/skills/release-assess/SKILL.md +11 -0
- package/skills/release-config/SKILL.md +6 -0
- package/skills/release-help/SKILL.md +20 -4
- package/skills/release-prepare/SKILL.md +11 -2
- package/skills/release-setup/SKILL.md +15 -2
- package/skills/release-verify/SKILL.md +2 -0
- package/skills-src/release-assess/SKILL.md +11 -0
- package/skills-src/release-config/SKILL.md +6 -0
- package/skills-src/release-help/SKILL.md +20 -4
- package/skills-src/release-prepare/SKILL.md +11 -2
- package/skills-src/release-setup/SKILL.md +15 -2
- package/skills-src/release-verify/SKILL.md +2 -0
- package/src/commands/approve.mjs +13 -0
- package/src/commands/prepare.mjs +501 -5
- package/src/commands/publish.mjs +2 -0
- package/src/commands/reconcile.mjs +2 -0
- package/src/commands/route.mjs +32 -7
- package/src/commands/setup.mjs +13 -6
- package/src/commands/ship.mjs +168 -5
- package/src/core/config.mjs +5 -0
- package/src/core/plan.mjs +3 -1
- package/src/core/test-candidate.mjs +649 -0
- package/src/core/test-intake.mjs +645 -0
package/src/commands/setup.mjs
CHANGED
|
@@ -3190,15 +3190,22 @@ export async function assessAdoption({
|
|
|
3190
3190
|
severity: 'blocking',
|
|
3191
3191
|
}));
|
|
3192
3192
|
}
|
|
3193
|
+
// requiredPublicFiles are string targets covered by publicFiles[].to.
|
|
3194
|
+
const publicFromByTo = new Map(
|
|
3195
|
+
publicFileMappings
|
|
3196
|
+
.filter((mapping) => typeof mapping?.to === 'string' && typeof mapping?.from === 'string')
|
|
3197
|
+
.map((mapping) => [mapping.to, mapping.from]),
|
|
3198
|
+
);
|
|
3193
3199
|
for (const required of unit.requiredPublicFiles ?? []) {
|
|
3194
|
-
if (typeof required
|
|
3195
|
-
|
|
3200
|
+
if (typeof required !== 'string') continue;
|
|
3201
|
+
const from = publicFromByTo.get(required);
|
|
3202
|
+
if (typeof from === 'string' && await adoptionPathExists(resolve(rootReal, from))) continue;
|
|
3196
3203
|
findings.push(createFinding({
|
|
3197
3204
|
category: FINDING_CATEGORY.MANDATORY_GAP, code: 'REQUIRED_PUBLIC_FILE_MISSING',
|
|
3198
|
-
unitId, fieldPath: 'releaseUnits[].requiredPublicFiles
|
|
3199
|
-
message: `发布单元 "${unitId}"
|
|
3200
|
-
evidence: { from:
|
|
3201
|
-
action: '创建该文件,或修正 releaseUnits[].requiredPublicFiles
|
|
3205
|
+
unitId, fieldPath: 'releaseUnits[].requiredPublicFiles',
|
|
3206
|
+
message: `发布单元 "${unitId}" 的必选公开文件不存在: ${required}`,
|
|
3207
|
+
evidence: { required, from: from ?? null, to: required },
|
|
3208
|
+
action: '创建该文件,或修正 releaseUnits[].requiredPublicFiles 与对应 publicFiles 映射。',
|
|
3202
3209
|
severity: 'blocking',
|
|
3203
3210
|
}));
|
|
3204
3211
|
}
|
package/src/commands/ship.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, lstat, mkdir } from 'node:fs/promises';
|
|
1
|
+
import { readFile, lstat, mkdir, realpath } from 'node:fs/promises';
|
|
2
2
|
import { basename, dirname, resolve } from 'node:path';
|
|
3
3
|
import { publishFileOrReplace } from 'skill-family-harness-node';
|
|
4
4
|
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
PLAN_DIGEST_MISMATCH,
|
|
22
22
|
CONSUMER_VERIFICATION_DEFERRED,
|
|
23
23
|
} from '../core/errors.mjs';
|
|
24
|
+
import {
|
|
25
|
+
applyShipPrepareOutcome,
|
|
26
|
+
resolveShipCandidateContinuation,
|
|
27
|
+
} from '../core/test-candidate.mjs';
|
|
24
28
|
|
|
25
29
|
function normalizeNewShipUnitIds(releaseUnits, requestedUnitIds) {
|
|
26
30
|
if (requestedUnitIds === undefined) return undefined;
|
|
@@ -102,6 +106,66 @@ async function readFrozenPlanForUnitScope(planPath, statePath, stateUnitIds) {
|
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
async function sameAuthorityFile(left, right) {
|
|
110
|
+
const identity = async (value) => {
|
|
111
|
+
const absolute = resolve(value);
|
|
112
|
+
try {
|
|
113
|
+
return await realpath(absolute);
|
|
114
|
+
} catch {
|
|
115
|
+
return absolute;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
return (await identity(left)) === (await identity(right));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A VERIFIED reattach keeps the release fact. An observed postverify is
|
|
123
|
+
* carried with its verify parent so open work can re-enter postVerifyRelease
|
|
124
|
+
* and completed work can feed the existing local finish checklist.
|
|
125
|
+
*/
|
|
126
|
+
async function observedPostverifyContinuation(root, planPath) {
|
|
127
|
+
if (!planPath) return null;
|
|
128
|
+
let routing;
|
|
129
|
+
try {
|
|
130
|
+
const { readRunRouting } = await import('./route.mjs');
|
|
131
|
+
routing = await readRunRouting(root, {});
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const formal = resolve(planPath);
|
|
136
|
+
const bound = [];
|
|
137
|
+
for (const run of routing?.observedRuns ?? []) {
|
|
138
|
+
if (!run?.planPath || !run.status || !run.runPath || !run.command) continue;
|
|
139
|
+
if (await sameAuthorityFile(run.planPath, formal)) bound.push(run);
|
|
140
|
+
}
|
|
141
|
+
const postverifyRuns = bound.filter((run) => run.command === 'postverify');
|
|
142
|
+
const completed = postverifyRuns.filter((run) => run.status === 'DISTRIBUTED');
|
|
143
|
+
const open = postverifyRuns.filter((run) => run.status !== 'DISTRIBUTED');
|
|
144
|
+
// A completed authority supersedes open attempts for the same verify
|
|
145
|
+
// parent. Multiple completed records are ambiguous; never fall back to an
|
|
146
|
+
// older open attempt or choose by directory order or timestamp.
|
|
147
|
+
if (completed.length > 1) return null;
|
|
148
|
+
const pending = completed.length === 1 ? completed[0] : open.length === 1 ? open[0] : null;
|
|
149
|
+
if (!pending) return null;
|
|
150
|
+
let verify = null;
|
|
151
|
+
for (const run of bound) {
|
|
152
|
+
if (run.command !== 'verify' || run.status !== 'VERIFIED' || !run.runPath) continue;
|
|
153
|
+
if (pending.sourceRunPath && await sameAuthorityFile(run.runPath, pending.sourceRunPath)) {
|
|
154
|
+
verify = run;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!verify) return null;
|
|
159
|
+
return {
|
|
160
|
+
verifyRunPath: verify.runPath,
|
|
161
|
+
...(verify.approvalPath ? { approvalPath: verify.approvalPath } : {}),
|
|
162
|
+
postVerify: {
|
|
163
|
+
status: pending.status,
|
|
164
|
+
runPath: pending.runPath,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
105
169
|
async function writeJsonAtomic(path, value) {
|
|
106
170
|
await mkdir(dirname(path), { recursive: true });
|
|
107
171
|
const { stateDigest: _oldDigest, ...body } = value;
|
|
@@ -177,9 +241,10 @@ async function defaultDependencies() {
|
|
|
177
241
|
|
|
178
242
|
function publicState(state, { postRelease } = {}) {
|
|
179
243
|
const visiblePostRelease = postRelease === undefined ? state.postRelease : postRelease;
|
|
244
|
+
const showsTestCandidate = state.status === 'NEW' && state.testCandidate;
|
|
180
245
|
return {
|
|
181
246
|
command: 'ship',
|
|
182
|
-
status: state.status,
|
|
247
|
+
status: showsTestCandidate ? 'NEEDS_INPUT' : state.status,
|
|
183
248
|
statePath: state.statePath,
|
|
184
249
|
targetVersion: state.targetVersion,
|
|
185
250
|
...(state.hooks && state.hooks.length > 0 ? {
|
|
@@ -199,6 +264,10 @@ function publicState(state, { postRelease } = {}) {
|
|
|
199
264
|
...(state.distributeRunPath ? { distributeRunPath: state.distributeRunPath } : {}),
|
|
200
265
|
...(state.postVerify ? { postVerify: state.postVerify } : {}),
|
|
201
266
|
...(state.requirements ? { requirements: state.requirements } : {}),
|
|
267
|
+
...(showsTestCandidate ? { testCandidate: state.testCandidate } : {}),
|
|
268
|
+
...(showsTestCandidate && state.pendingTestRequirements
|
|
269
|
+
? { pendingTestRequirements: state.pendingTestRequirements }
|
|
270
|
+
: {}),
|
|
202
271
|
...(state.manualFollowUps ? { manualFollowUps: state.manualFollowUps } : {}),
|
|
203
272
|
...(state.metadataUpdate ? { metadataUpdate: state.metadataUpdate } : {}),
|
|
204
273
|
...(visiblePostRelease ? { postRelease: visiblePostRelease } : {}),
|
|
@@ -483,7 +552,77 @@ export async function advanceShip(options = {}, injected = {}) {
|
|
|
483
552
|
});
|
|
484
553
|
}
|
|
485
554
|
|
|
486
|
-
if (state.status === 'NEW') {
|
|
555
|
+
if (state.status === 'NEW' && state.testCandidate) {
|
|
556
|
+
const loaded = await deps.loadProjectConfig({ root });
|
|
557
|
+
const continuation = resolveShipCandidateContinuation({
|
|
558
|
+
state,
|
|
559
|
+
loadedConfigDigest: loaded.configDigest,
|
|
560
|
+
requestedUnitIds: options.unitIds,
|
|
561
|
+
});
|
|
562
|
+
if (continuation.kind === 'conflict') {
|
|
563
|
+
throw new ReleaseError(GATE_FAILED, `ship test candidate conflict: ${continuation.reason}`, {
|
|
564
|
+
reason: continuation.reason,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const prepared = await deps.prepareRelease({
|
|
568
|
+
root,
|
|
569
|
+
candidatePath: continuation.candidatePath,
|
|
570
|
+
expectedCandidateSha256: continuation.candidateSha256,
|
|
571
|
+
expectedSubjectDigest: continuation.subjectDigest,
|
|
572
|
+
production: true,
|
|
573
|
+
offline: false,
|
|
574
|
+
offlineExplicit: false,
|
|
575
|
+
workflowExplicit: false,
|
|
576
|
+
version: state.targetVersion ?? undefined,
|
|
577
|
+
versionExplicit: state.targetVersion != null,
|
|
578
|
+
hooksAuthorized: true,
|
|
579
|
+
verificationGatesAuthorized: true,
|
|
580
|
+
hookCache: true,
|
|
581
|
+
...(Array.isArray(state.selectedUnitIds) ? { unitIds: state.selectedUnitIds } : {}),
|
|
582
|
+
});
|
|
583
|
+
if (prepared?.status === 'NEEDS_INPUT') {
|
|
584
|
+
state = {
|
|
585
|
+
...applyShipPrepareOutcome(state, {
|
|
586
|
+
...prepared,
|
|
587
|
+
testCandidate: {
|
|
588
|
+
path: prepared.candidatePath ?? state.testCandidate.path,
|
|
589
|
+
sha256: prepared.candidateSha256 ?? state.testCandidate.sha256,
|
|
590
|
+
subjectDigest: prepared.subjectDigest ?? state.testCandidate.subjectDigest,
|
|
591
|
+
evidenceDir: prepared.evidenceDir ?? state.testCandidate.evidenceDir,
|
|
592
|
+
},
|
|
593
|
+
}),
|
|
594
|
+
updatedAt: new Date().toISOString(),
|
|
595
|
+
};
|
|
596
|
+
await writeJsonAtomic(statePath, state);
|
|
597
|
+
return publicState(state);
|
|
598
|
+
}
|
|
599
|
+
const outcome = applyShipPrepareOutcome(state, prepared);
|
|
600
|
+
// A reattached PARTIAL, PUBLISHED, or VERIFIED record stays that status.
|
|
601
|
+
// Returning here avoids a new approval and avoids falling through into
|
|
602
|
+
// reconcile, distribute, or verify in this same call.
|
|
603
|
+
if (outcome.status !== 'NEEDS_PLAN_APPROVAL') {
|
|
604
|
+
const continuation = outcome.status === 'VERIFIED'
|
|
605
|
+
? await observedPostverifyContinuation(root, outcome.planPath)
|
|
606
|
+
: null;
|
|
607
|
+
state = {
|
|
608
|
+
...outcome,
|
|
609
|
+
...(continuation ?? {}),
|
|
610
|
+
updatedAt: new Date().toISOString(),
|
|
611
|
+
};
|
|
612
|
+
await writeJsonAtomic(statePath, state);
|
|
613
|
+
return publicState(state);
|
|
614
|
+
}
|
|
615
|
+
const maxApprovalMs = typeof deps.readMaxApprovalMs === 'function'
|
|
616
|
+
? await deps.readMaxApprovalMs()
|
|
617
|
+
: MAX_APPROVAL_MS;
|
|
618
|
+
const approvalSummary = await buildApprovalSummary(prepared.planPath, maxApprovalMs);
|
|
619
|
+
state = {
|
|
620
|
+
...outcome,
|
|
621
|
+
approvalSummary,
|
|
622
|
+
updatedAt: new Date().toISOString(),
|
|
623
|
+
};
|
|
624
|
+
await writeJsonAtomic(statePath, state);
|
|
625
|
+
} else if (state.status === 'NEW') {
|
|
487
626
|
const loaded = await deps.loadProjectConfig({ root });
|
|
488
627
|
let stateChanged = false;
|
|
489
628
|
if (Array.isArray(state.selectedUnitIds)) {
|
|
@@ -538,6 +677,23 @@ export async function advanceShip(options = {}, injected = {}) {
|
|
|
538
677
|
hookCache: true,
|
|
539
678
|
...(state.selectedUnitIds ? { unitIds: state.selectedUnitIds } : {}),
|
|
540
679
|
});
|
|
680
|
+
if (prepared?.status === 'NEEDS_INPUT') {
|
|
681
|
+
state = {
|
|
682
|
+
...applyShipPrepareOutcome(state, {
|
|
683
|
+
...prepared,
|
|
684
|
+
testCandidate: {
|
|
685
|
+
path: prepared.candidatePath,
|
|
686
|
+
sha256: prepared.candidateSha256,
|
|
687
|
+
subjectDigest: prepared.subjectDigest,
|
|
688
|
+
evidenceDir: prepared.evidenceDir,
|
|
689
|
+
},
|
|
690
|
+
requirements: prepared.requirements,
|
|
691
|
+
}),
|
|
692
|
+
updatedAt: new Date().toISOString(),
|
|
693
|
+
};
|
|
694
|
+
await writeJsonAtomic(statePath, state);
|
|
695
|
+
return publicState(state);
|
|
696
|
+
}
|
|
541
697
|
let frozenPlan = null;
|
|
542
698
|
if (Array.isArray(state.selectedUnitIds) || deps.preflightGitTransports) {
|
|
543
699
|
frozenPlan = Array.isArray(state.selectedUnitIds)
|
|
@@ -631,6 +787,9 @@ export async function advanceShip(options = {}, injected = {}) {
|
|
|
631
787
|
}
|
|
632
788
|
|
|
633
789
|
if (state.status === 'PARTIAL') {
|
|
790
|
+
// A reattached terminal record has no source run. Returning keeps that
|
|
791
|
+
// status; a publish that stored sourceRunPath still reconciles.
|
|
792
|
+
if (!state.sourceRunPath) return publicState(state);
|
|
634
793
|
const reconciled = await deps.reconcileRelease({
|
|
635
794
|
planPath: state.planPath,
|
|
636
795
|
sourceRunPath: state.sourceRunPath,
|
|
@@ -653,6 +812,7 @@ export async function advanceShip(options = {}, injected = {}) {
|
|
|
653
812
|
let postVerifyRanThisCall = false;
|
|
654
813
|
|
|
655
814
|
if (state.status === 'PUBLISHED' || state.status === 'NEEDS_MANUAL_ATTESTATIONS') {
|
|
815
|
+
if (!state.sourceRunPath && !state.distributeRunPath) return publicState(state);
|
|
656
816
|
// Step 1: Check if postPublish requires distribution.
|
|
657
817
|
// Only targets and distribute-phase hooks route through distribute;
|
|
658
818
|
// phase:postVerify hooks belong to their independent post-VERIFIED run.
|
|
@@ -699,10 +859,12 @@ export async function advanceShip(options = {}, injected = {}) {
|
|
|
699
859
|
}
|
|
700
860
|
|
|
701
861
|
// Step 2: Verify (this will check for distributed run if distribution was required)
|
|
862
|
+
const verifySource = needsDistribution ? state.distributeRunPath : state.sourceRunPath;
|
|
863
|
+
if (!verifySource) return publicState(state);
|
|
702
864
|
try {
|
|
703
865
|
const verified = await deps.verifyRelease({
|
|
704
866
|
planPath: state.planPath,
|
|
705
|
-
sourceRunPath:
|
|
867
|
+
sourceRunPath: verifySource,
|
|
706
868
|
adapterRegistry: options.adapterRegistry,
|
|
707
869
|
root,
|
|
708
870
|
verificationGatesAuthorized: state.verificationGatesAuthorized === true,
|
|
@@ -737,7 +899,7 @@ export async function advanceShip(options = {}, injected = {}) {
|
|
|
737
899
|
const postVerifyHooks = normalizePostPublishView(plan)
|
|
738
900
|
.flatMap((declaration) => declaration.hooks ?? [])
|
|
739
901
|
.filter((hook) => hook.phase === 'postVerify');
|
|
740
|
-
if (state.status === 'VERIFIED' && postVerifyHooks.length > 0 && deps.postVerifyRelease) {
|
|
902
|
+
if (state.status === 'VERIFIED' && state.verifyRunPath && postVerifyHooks.length > 0 && deps.postVerifyRelease) {
|
|
741
903
|
postVerifyRanThisCall = true;
|
|
742
904
|
let postVerifyOutcome;
|
|
743
905
|
try {
|
|
@@ -785,6 +947,7 @@ export async function advanceShip(options = {}, injected = {}) {
|
|
|
785
947
|
if (
|
|
786
948
|
state.status === 'VERIFIED'
|
|
787
949
|
&& deps.postVerifyRelease
|
|
950
|
+
&& state.verifyRunPath
|
|
788
951
|
&& !postVerifyRanThisCall
|
|
789
952
|
&& (!state.postVerify || state.postVerify.status !== 'DISTRIBUTED')
|
|
790
953
|
) {
|
package/src/core/config.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import { validatePublicSurfaceGlob } from './public-surface.mjs';
|
|
|
27
27
|
import { canonicalPublicPath, publicPathCollisionKey } from '../snapshot/public-path.mjs';
|
|
28
28
|
import { isReservedReleaseControlPath } from './baseline.mjs';
|
|
29
29
|
import { readTrustedPackageResource } from './trusted-resource.mjs';
|
|
30
|
+
import { assertTestIntakePaths } from './test-intake.mjs';
|
|
30
31
|
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
33
|
// Constants
|
|
@@ -423,6 +424,10 @@ export async function loadProjectConfig({ root, configPath } = {}) {
|
|
|
423
424
|
);
|
|
424
425
|
}
|
|
425
426
|
|
|
427
|
+
if (config.testIntake) {
|
|
428
|
+
assertTestIntakePaths(config.testIntake);
|
|
429
|
+
}
|
|
430
|
+
|
|
426
431
|
// --- Compile forbiddenContentPatterns (fail closed on invalid regex) ---
|
|
427
432
|
const forbiddenContentPatterns = config.policy?.forbiddenContentPatterns ?? [];
|
|
428
433
|
for (const pattern of forbiddenContentPatterns) {
|
package/src/core/plan.mjs
CHANGED
|
@@ -210,7 +210,9 @@ export function validatePlan(plan) {
|
|
|
210
210
|
* still written to the plan file (schema unchanged, evidence chain intact)
|
|
211
211
|
* but is excluded from the digest. Everything else (planVersion, units with
|
|
212
212
|
* every frozenSnapshot artifact identity field, externalActions identity,
|
|
213
|
-
* production, configDigest, verificationGates, snapshotDigest
|
|
213
|
+
* production, configDigest, verificationGates, snapshotDigest, and
|
|
214
|
+
* testAcceptance when present) binds. testAcceptance is intentionally absent
|
|
215
|
+
* from the exclusion list below: it stays in the binding layer.
|
|
214
216
|
* For v3 the frozen `postPublish` array and every item binding field stay in
|
|
215
217
|
* the binding layer: any order or binding change moves the digest.
|
|
216
218
|
*
|