create-yss-spec 3.3.6 → 3.3.7
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/README.md +2 -2
- package/package.json +1 -1
- package/src/template/instance-runtime.js +8 -2
- package/src/validation/snapshot.js +7 -0
- package/template/.agents/skills/.strategic-design-skills-manifest.json +5 -3
- package/template/.agents/skills/archify/SKILL.md +1 -1
- package/template/.agents/skills/yss-stage-decision/SKILL.md +13 -4
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
- package/template/.agents/skills/yss-stage-decision/references/validation-rules.md +2 -2
- package/template/.agents/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
- package/template/.agents/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
- package/template/.agents/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
- package/template/.agents/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
- package/template/.codex/skills/archify/SKILL.md +1 -1
- package/template/.codex/skills/yss-stage-decision/SKILL.md +13 -4
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
- package/template/.codex/skills/yss-stage-decision/references/validation-rules.md +2 -2
- package/template/.codex/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
- package/template/.codex/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
- package/template/.codex/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
- package/template/.codex/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
- package/template/.cursor/skills/archify/SKILL.md +1 -1
- package/template/.cursor/skills/yss-stage-decision/SKILL.md +13 -4
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
- package/template/.cursor/skills/yss-stage-decision/references/validation-rules.md +2 -2
- package/template/.cursor/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
- package/template/.cursor/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
- package/template/.pi/skills/archify/SKILL.md +1 -1
- package/template/.pi/skills/yss-stage-decision/SKILL.md +13 -4
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
- package/template/.pi/skills/yss-stage-decision/references/validation-rules.md +2 -2
- package/template/.pi/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
- package/template/.pi/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
- package/template/.pi/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
- package/template/.pi/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
- package/template/docs/process/frontend-backend-delivery.md +7 -4
- package/template/docs/process/schemas/backend-delivery.schema.json +4 -0
- package/template/docs/process/schemas/frontend-delivery-acceptance-v1.schema.json +200 -0
- package/template/docs/process/schemas/frontend-delivery-acceptance.schema.json +37 -3
- package/template/docs/process/schemas/frontend-strategic-preflight.schema.json +32 -0
- package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +2 -1
- package/template/docs/process/schemas/strategic-consumer-feedback.schema.json +40 -0
- package/template/docs/process/schemas/strategic-design-handoff-v3.schema.json +743 -0
- package/template/docs/process/schemas/strategic-design-handoff-v4.schema.json +712 -0
- package/template/docs/process/schemas/strategic-feedback-adjudication.schema.json +42 -0
- package/template/docs/process/schemas/strategic-handoff-domain-strategy-v3.schema.json +216 -0
- package/template/docs/process/schemas/strategic-handoff-import-receipt.schema.json +39 -0
- package/template/docs/process/schemas/strategic-handoff-stage-decision-package-v3.schema.json +148 -0
- package/template/docs/process/strategic-handoff-package.md +37 -4
- package/template/docs/process/templates/lifecycle-checkpoint-template.yaml +1 -0
- package/template/docs/process/templates/stage-checkpoint-template.md +1 -0
- package/template/scripts/lib/approval-record.mjs +2 -1
- package/template/scripts/lib/backend-delivery.mjs +15 -3
- package/template/scripts/lib/frontend-delivery-boundary.mjs +20 -4
- package/template/scripts/lib/frontend-delivery.mjs +66 -2
- package/template/scripts/lib/strategic-feedback.mjs +33 -0
- package/template/scripts/lib/strategic-handoff-consumption.mjs +5 -0
- package/template/scripts/lib/strategic-handoff-io.mjs +1 -0
- package/template/scripts/lib/strategic-handoff.mjs +101 -10
- package/template/scripts/lib/user-decision-reuse.mjs +38 -0
- package/template/scripts/strategic-feedback +10 -0
- package/template/scripts/verify-frontend-strategic-preflight +12 -0
- package/template/skills-lock.json +10 -10
- package/template.manifest.json +1 -0
- package/template.snapshot.json +6 -5
|
@@ -7,14 +7,27 @@ import { ROOT, read, safe, ensure, digest } from './strategic-handoff-io.mjs';
|
|
|
7
7
|
// Shared synchronous adapter for existing compiler, lifecycle and task-package seams.
|
|
8
8
|
// Always re-executes the verifier; a caller-supplied "verified" flag is never evidence.
|
|
9
9
|
export function enforceFrontendDelivery(state={}, {root=ROOT,phase='inputs'}={}) {
|
|
10
|
+
ensure(['preflight','design','contract','inputs','implementation','verification'].includes(phase),`未知前端交付阶段: ${phase}`);
|
|
10
11
|
const profile=loadDeliveryProfile(root);
|
|
11
12
|
let contract;
|
|
12
13
|
const contractRef=state.slice_contract_ref||state.contract?.slice_contract_ref;
|
|
13
|
-
const
|
|
14
|
+
const dedicatedProfile=profile?.profile_id==='harness.frontend-delivery'||profile?.profile_id==='yss-harness-frontend';
|
|
15
|
+
const selected=dedicatedProfile||profile?.frontend_delivery?.required===true||profile?.handoff?.consumer_capabilities?.includes('frontend-engineering-design');
|
|
14
16
|
const identity=selected?read(safe(root,'yss-project.yaml')):null;
|
|
15
17
|
if(selected)ensure(identity.schema_version===1&&['template-source','project-instance'].includes(identity.repository_mode),'前端项目仓库身份无效');
|
|
16
|
-
const dedicated=
|
|
18
|
+
const dedicated=dedicatedProfile&&identity.repository_mode==='project-instance';
|
|
17
19
|
if(dedicated)ensure(state.contract?.kind!=='template-maintenance','前端产品项目不得用模板维护任务绕过输入条件');
|
|
20
|
+
if(['preflight','design'].includes(phase)) {
|
|
21
|
+
const preflight=state.frontend_preflight||state.frontend?.preflight||state.resolution?.frontend_preflight;
|
|
22
|
+
ensure(preflight?.preflight_ref,'frontend-preflight-required: 缺少 Frontend Strategic Preflight');
|
|
23
|
+
const args=[path.join(ROOT,'scripts/verify-frontend-strategic-preflight'),'--root',root];
|
|
24
|
+
if(preflight.digest)args.push('--expected-digest',preflight.digest);
|
|
25
|
+
args.push(preflight.preflight_ref);
|
|
26
|
+
const verified=spawnSync(process.execPath,args,{cwd:root,encoding:'utf8',timeout:60000,maxBuffer:2*1024*1024});
|
|
27
|
+
ensure(verified.status===0,`frontend-preflight-blocked: ${verified.error?.message||verified.stderr||verified.stdout}`);
|
|
28
|
+
const result=JSON.parse(verified.stdout);ensure(result.result==='preflight-verified'&&result.ready_for_agent===false,'前端战略预检返回非法状态');
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
18
31
|
let binding=state.frontend_delivery||state.frontend?.delivery||state.resolution?.frontend_delivery;
|
|
19
32
|
if(contractRef) {
|
|
20
33
|
// Legacy callers may supply an absolute external contract. Discover whether
|
|
@@ -27,17 +40,20 @@ export function enforceFrontendDelivery(state={}, {root=ROOT,phase='inputs'}={})
|
|
|
27
40
|
binding=direct||resolved||binding;
|
|
28
41
|
}
|
|
29
42
|
}
|
|
30
|
-
|
|
43
|
+
const frontendStatus=contract?.frontend?.status||state.frontend?.status;
|
|
44
|
+
const contractRequiresFrontend=typeof frontendStatus==='string'&&!['not-applicable','disabled'].includes(frontendStatus);
|
|
45
|
+
if(!dedicated&&!binding&&!contractRequiresFrontend)return {result:'not-applicable'};
|
|
31
46
|
if(contractRef)safe(root,contractRef);
|
|
32
47
|
ensure(binding?.acceptance_ref,'frontend-delivery-required: 缺少战略与后端联合接收记录');
|
|
33
48
|
const slice=contract?.slice_id||state.slice_id;
|
|
34
49
|
ensure(!contract?.slice_id||!state.slice_id||contract.slice_id===state.slice_id,'前端任务与 Slice Contract 切片不一致');
|
|
35
50
|
ensure(slice,'frontend-delivery-required: 缺少明确切片');
|
|
36
|
-
if(
|
|
51
|
+
if(['implementation','verification'].includes(phase)) {
|
|
37
52
|
ensure(contract?.status==='approved','frontend-delivery-required: 必须读取已持久化的 approved Slice Contract');
|
|
38
53
|
ensure(contract.frontend?.delivery||contract.resolution?.frontend_delivery,'frontend-delivery-required: Slice Contract 未绑定前端交付');
|
|
39
54
|
ensure(binding.digest,'frontend-delivery-required: Slice Contract 未冻结接收记录摘要');
|
|
40
55
|
}
|
|
56
|
+
if(phase==='verification')ensure(state.frontend_implementation_verification_ref||contract?.frontend?.implementation_verification_ref,'frontend-delivery-required: 验证阶段缺少 frontend_implementation_verification 引用');
|
|
41
57
|
const args=[path.join(ROOT,'scripts/verify-frontend-delivery'),'--root',root,'--slice',slice];
|
|
42
58
|
if(binding.digest)args.push('--expected-digest',binding.digest);
|
|
43
59
|
args.push(binding.acceptance_ref);
|
|
@@ -1,9 +1,49 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
3
4
|
import { openBackendDelivery } from './backend-delivery.mjs';
|
|
4
5
|
import { openBundle } from './strategic-handoff.mjs';
|
|
5
6
|
import { verifyConsumption } from './strategic-handoff-consumption.mjs';
|
|
6
|
-
import { read, safe, ensure, hash, schema, project } from './strategic-handoff-io.mjs';
|
|
7
|
+
import { read, safe, ensure, hash, schema, project, ROOT } from './strategic-handoff-io.mjs';
|
|
8
|
+
|
|
9
|
+
const ACCEPTANCE_SCHEMAS=new Map([[1,'docs/process/schemas/frontend-delivery-acceptance-v1.schema.json'],[2,'docs/process/schemas/frontend-delivery-acceptance.schema.json']]);
|
|
10
|
+
|
|
11
|
+
function acceptanceSchema(version) {
|
|
12
|
+
ensure(ACCEPTANCE_SCHEMAS.has(version),`Frontend Delivery Acceptance 未知 schema_version: ${String(version)};支持版本: 1, 2;新接收请迁移到 2`);
|
|
13
|
+
return ACCEPTANCE_SCHEMAS.get(version);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function verifyFrontendStrategicPreflight({root=process.cwd(),preflightRef,expectedDigest}={}) {
|
|
17
|
+
project(root);
|
|
18
|
+
const file=safe(root,preflightRef),preflight=read(file);
|
|
19
|
+
schema(preflight,'docs/process/schemas/frontend-strategic-preflight.schema.json');
|
|
20
|
+
const preflightDigest=hash(readFileSync(file));
|
|
21
|
+
if(expectedDigest)ensure(expectedDigest===preflightDigest,'前端战略预检摘要已变化,需重编译合同');
|
|
22
|
+
ensure(preflight.status==='verified','前端战略预检尚未 verified');
|
|
23
|
+
const receipt=read(safe(root,preflight.import_receipt_ref));
|
|
24
|
+
ensure(receipt.schema_version===2,'Frontend Strategic Preflight 仅接受 Import Receipt v2');
|
|
25
|
+
const base=`docs/handoffs/${receipt.bundle_id}/${receipt.version}`;
|
|
26
|
+
ensure(preflight.import_receipt_ref===`${base}/import-receipt.json`&&receipt.package_ref===`${base}/package`,'战略预检收据路径与身份不匹配');
|
|
27
|
+
ensure(preflight.bundle_digest===receipt.bundle_digest,'战略预检与收据摘要不一致');
|
|
28
|
+
const routeReceipt=receipt.routes.find(item=>item.route_id===preflight.route_id&&item.capability==='frontend-engineering-design');
|
|
29
|
+
ensure(routeReceipt&&routeReceipt.activation!=='not-applicable','当前 Handoff 未启用前端工程设计路由');
|
|
30
|
+
const reconciliation=spawnSync(process.execPath,[path.join(ROOT,'scripts/verify-context-reconciliation'),'--root',root,safe(root,preflight.context_reconciliation_ref)],{encoding:'utf8'});
|
|
31
|
+
ensure(reconciliation.status===0,`前端战略预检 Context Reconciliation 未通过: ${reconciliation.error?.message||reconciliation.stderr}`);
|
|
32
|
+
return openBundle(safe(root,receipt.package_ref),bundle=>{
|
|
33
|
+
ensure(bundle.handoff.schema_version===4&&bundle.manifest.bundle_digest===receipt.bundle_digest,'前端战略预检必须绑定 Handoff v4 当前包');
|
|
34
|
+
const frontendRoute=bundle.handoff.consumer_routes.find(item=>item.route_id===preflight.route_id&&item.capability==='frontend-engineering-design');
|
|
35
|
+
const backendRoute=bundle.handoff.consumer_routes.find(item=>item.capability==='backend-technical-design');
|
|
36
|
+
ensure(frontendRoute&&frontendRoute.activation!=='not-applicable','前端路由未启用');
|
|
37
|
+
const known=[...bundle.indexes.rules.map(item=>item.rule_id),...bundle.indexes.scenarios.filter(item=>item.critical).map(item=>item.scenario_id)].sort();
|
|
38
|
+
ensure(JSON.stringify([...preflight.source_rule_refs].sort())===JSON.stringify(known),'前端战略预检未完整绑定源规则/关键场景');
|
|
39
|
+
const expectedVisual=`${base}/package/payload/files/${bundle.handoff.source.visual_baseline_ref.persisted_ref}/${bundle.handoff.source.visual_baseline_ref.manifest_ref}`;
|
|
40
|
+
ensure(preflight.visual_baseline_ref===expectedVisual,'前端战略预检 Visual Baseline 引用不一致');
|
|
41
|
+
const expectedMode=backendRoute.activation==='not-applicable'?'not-applicable':'required';
|
|
42
|
+
ensure(preflight.backend_dependency.mode===expectedMode&&preflight.backend_dependency.route_id===backendRoute.route_id,'前端战略预检后端依赖与消费者路由不一致');
|
|
43
|
+
if(expectedMode==='not-applicable')ensure(preflight.backend_dependency.reason===backendRoute.reason&&JSON.stringify(preflight.backend_dependency.impact_refs)===JSON.stringify(backendRoute.impact_refs)&&JSON.stringify(preflight.backend_dependency.evidence_refs)===JSON.stringify(backendRoute.evidence_refs),'backend-not-applicable 依据与战略路由不一致');
|
|
44
|
+
return {result:'preflight-verified',ready_for_agent:false,preflight_ref:preflightRef,preflight_digest:preflightDigest,strategic_bundle_digest:receipt.bundle_digest,backend_dependency:expectedMode,next_action:'prepare-frontend-engineering-design-draft'};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
7
47
|
|
|
8
48
|
function pointer(value, ref) {
|
|
9
49
|
ensure(ref.startsWith('/')&&!ref.includes('//'),'服务版本 JSON Pointer 无效');
|
|
@@ -35,10 +75,33 @@ export async function probeBackend(delivery) {
|
|
|
35
75
|
export async function verifyFrontendDelivery({root=process.cwd(),acceptanceRef,sliceRef,expectedDigest}) {
|
|
36
76
|
project(root);
|
|
37
77
|
const file=safe(root,acceptanceRef), acceptance=read(file);
|
|
38
|
-
schema(acceptance,
|
|
78
|
+
schema(acceptance,acceptanceSchema(acceptance?.schema_version));
|
|
39
79
|
const acceptanceDigest=hash(readFileSync(file));
|
|
40
80
|
if(expectedDigest)ensure(expectedDigest===acceptanceDigest,'前端接收记录摘要已变化,需重编译合同');
|
|
41
81
|
ensure(sliceRef&&acceptance.slice_id===sliceRef,'前端接收记录与执行切片不匹配');
|
|
82
|
+
if(acceptance.schema_version===2) {
|
|
83
|
+
const preflight=await verifyFrontendStrategicPreflight({root,preflightRef:acceptance.strategic_preflight.ref,expectedDigest:acceptance.strategic_preflight.digest});
|
|
84
|
+
ensure(preflight.strategic_bundle_digest===acceptance.strategic_handoff.bundle_digest,'前端接收与战略预检版本不一致');
|
|
85
|
+
if(acceptance.backend_dependency.mode==='not-applicable') {
|
|
86
|
+
ensure(!acceptance.backend_delivery,'backend-not-applicable 不得绑定后端交付收据');
|
|
87
|
+
const strategic=await verifyConsumption(acceptance,{root,sliceRef,consumer:'frontend'});
|
|
88
|
+
ensure(strategic.result==='verified',`战略承接阻断: ${JSON.stringify(strategic.issues)}`);
|
|
89
|
+
const strategicReceipt=read(safe(root,acceptance.strategic_handoff.import_receipt_ref));
|
|
90
|
+
return openBundle(safe(root,strategicReceipt.package_ref),bundle=>{
|
|
91
|
+
const visualCases=bundle.handoff.source.visual_baseline_ref.case_ids;
|
|
92
|
+
const sourceIds=new Set([...bundle.indexes.rules.map(item=>item.rule_id),...bundle.indexes.scenarios.filter(item=>item.critical).map(item=>item.scenario_id)]);
|
|
93
|
+
const ids=acceptance.frontend_cases.map(item=>item.case_id);ensure(new Set(ids).size===ids.length,'前端验收用例 ID 重复');
|
|
94
|
+
for(const item of acceptance.frontend_cases){
|
|
95
|
+
ensure(item.operation_ids.length===0,'backend-not-applicable 前端用例不得绑定 operation_ids');
|
|
96
|
+
ensure(item.source_ids.every(id=>sourceIds.has(id)),'前端用例引用未知规则/场景');
|
|
97
|
+
ensure(item.visual_case_ids.every(id=>visualCases.includes(id)),'前端用例视觉基线悬空');
|
|
98
|
+
const evidence=readFileSync(safe(root,item.evidence_ref));ensure(evidence.length>0&&hash(evidence)===item.evidence_digest,'前端承接用例说明为空或摘要漂移');
|
|
99
|
+
}
|
|
100
|
+
return {result:'inputs-verified',ready_for_agent:false,slice_id:sliceRef,acceptance_ref:acceptanceRef,acceptance_digest:acceptanceDigest,strategic_bundle_digest:strategic.consumed_bundle_digest,backend_dependency:'not-applicable',next_action:'prepare-or-revalidate-frontend-plan-and-slice-contract'};
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
ensure(acceptance.backend_dependency.mode==='required','frontend backend_dependency.mode 非法');
|
|
104
|
+
}
|
|
42
105
|
const binding=acceptance.backend_delivery, receipt=read(safe(root,binding.import_receipt_ref));
|
|
43
106
|
const base=`docs/backend-deliveries/${receipt.delivery_id}/${receipt.version}`;
|
|
44
107
|
ensure(binding.import_receipt_ref===`${base}/import-receipt.json`&&receipt.package_ref===`${base}/package`,'后端收据路径与身份不匹配');
|
|
@@ -56,6 +119,7 @@ export async function verifyFrontendDelivery({root=process.cwd(),acceptanceRef,s
|
|
|
56
119
|
await openBundle(safe(backend.source,delivery.strategic_bundle_ref),bundle=>{
|
|
57
120
|
const visualCases=bundle.handoff.source.visual_baseline_ref.case_ids;
|
|
58
121
|
for(const item of acceptance.frontend_cases) {
|
|
122
|
+
if(acceptance.schema_version===2)ensure(item.operation_ids.length>0,'有后端依赖时前端用例必须绑定已交付 operation_ids');
|
|
59
123
|
ensure(item.operation_ids.every(id=>delivery.scope.operation_ids.includes(id)),'前端用例依赖未交付接口');
|
|
60
124
|
ensure(item.source_ids.every(id=>delivery.scope.source_ids.includes(id)),'前端用例依赖未交付规则/场景');
|
|
61
125
|
ensure(item.visual_case_ids.every(id=>visualCases.includes(id)),'前端用例视觉基线悬空');
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { openBundle } from './strategic-handoff.mjs';
|
|
3
|
+
import { read, safe, ensure, hash, schema, project } from './strategic-handoff-io.mjs';
|
|
4
|
+
|
|
5
|
+
export async function verifyConsumerFeedback({root=process.cwd(),feedbackRef}={}) {
|
|
6
|
+
project(root);const file=safe(root,feedbackRef),feedback=read(file);
|
|
7
|
+
schema(feedback,'docs/process/schemas/strategic-consumer-feedback.schema.json');
|
|
8
|
+
const receipt=read(safe(root,feedback.import_receipt_ref));
|
|
9
|
+
ensure(receipt.schema_version===2,'Consumer Feedback v1 只接受 Handoff v4 的 Import Receipt v2');
|
|
10
|
+
const base=`docs/handoffs/${receipt.bundle_id}/${receipt.version}`;
|
|
11
|
+
ensure(feedback.import_receipt_ref===`${base}/import-receipt.json`&&receipt.package_ref===`${base}/package`,'Consumer Feedback 导入收据路径无效');
|
|
12
|
+
ensure(feedback.bundle.id===receipt.bundle_id&&feedback.bundle.version===receipt.version&&feedback.bundle.digest===receipt.bundle_digest,'Consumer Feedback 绑定的 bundle 身份或摘要不一致');
|
|
13
|
+
const route=receipt.routes.find(item=>item.route_id===feedback.route_id);ensure(route&&route.activation!=='not-applicable','Consumer Feedback route_id 未被当前消费者激活');
|
|
14
|
+
for(const ref of feedback.evidence_refs)ensure(readFileSync(safe(root,ref)).length>0,`Consumer Feedback evidence 为空: ${ref}`);
|
|
15
|
+
return openBundle(safe(root,receipt.package_ref),bundle=>{
|
|
16
|
+
ensure(bundle.handoff.schema_version===4&&bundle.manifest.bundle_digest===receipt.bundle_digest,'Consumer Feedback 必须绑定当前 Handoff v4 包');
|
|
17
|
+
const source=[...bundle.indexes.rules.map(item=>({id:item.rule_id,digest:item.source_digest})),...bundle.indexes.scenarios.map(item=>({id:item.scenario_id,digest:item.source_digest}))].find(item=>item.id===feedback.source.id);
|
|
18
|
+
ensure(source&&source.digest===feedback.source.digest,'Consumer Feedback source ID 或摘要不是当前包内容');
|
|
19
|
+
return {result:'feedback-verified',feedback_ref:feedbackRef,feedback_digest:hash(readFileSync(file)),severity:feedback.severity,route_id:feedback.route_id,bundle_digest:receipt.bundle_digest};
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function verifyFeedbackAdjudication({root=process.cwd(),adjudicationRef}={}) {
|
|
24
|
+
project(root);const file=safe(root,adjudicationRef),adjudication=read(file);
|
|
25
|
+
schema(adjudication,'docs/process/schemas/strategic-feedback-adjudication.schema.json');
|
|
26
|
+
const feedbackFile=safe(root,adjudication.feedback.ref);ensure(hash(readFileSync(feedbackFile))===adjudication.feedback.digest,'Feedback Adjudication 绑定的 feedback digest 已失效');
|
|
27
|
+
const verified=await verifyConsumerFeedback({root,feedbackRef:adjudication.feedback.ref});
|
|
28
|
+
for(const ref of adjudication.evidence_refs)ensure(readFileSync(safe(root,ref)).length>0,`Feedback Adjudication evidence 为空: ${ref}`);
|
|
29
|
+
if(adjudication.approval_ref)ensure(readFileSync(safe(root,adjudication.approval_ref)).length>0,'Feedback Adjudication approval_ref 为空');
|
|
30
|
+
if(adjudication.decision==='amend')return {result:'blocked',decision:'amend',requires_new_handoff:true,invalidated_bundle_digest:verified.bundle_digest,next_work_unit:adjudication.return_work_unit,target_version:adjudication.target_version};
|
|
31
|
+
if(adjudication.decision==='defer'&&verified.severity==='blocking')return {result:'blocked',decision:'defer',requires_new_handoff:false,next_work_unit:adjudication.return_work_unit,target_version:adjudication.target_version};
|
|
32
|
+
return {result:'adjudicated',decision:adjudication.decision,may_continue:true,bundle_digest:verified.bundle_digest,target_version:adjudication.target_version};
|
|
33
|
+
}
|
|
@@ -23,6 +23,11 @@ export async function verifyConsumption(data,{root=process.cwd(),sliceRef,consum
|
|
|
23
23
|
const expectedBase=`docs/handoffs/${receipt.bundle_id}/${receipt.version}`;
|
|
24
24
|
ensure(binding.import_receipt_ref===`${expectedBase}/import-receipt.json` && receipt.package_ref===`${expectedBase}/package`,'导入收据路径与包身份不一致');
|
|
25
25
|
ensure(binding.bundle_digest===receipt.bundle_digest,'战术绑定与导入收据摘要不一致');
|
|
26
|
+
if(receipt.schema_version===2){
|
|
27
|
+
const capability=consumer==='frontend'?'frontend-engineering-design':'backend-technical-design';
|
|
28
|
+
const route=receipt.routes.find(item=>item.capability===capability);
|
|
29
|
+
ensure(route&&route.activation!=='not-applicable'&&binding.route_id===route.route_id,`${capability} 未绑定当前 Handoff v4 route_id`);
|
|
30
|
+
}
|
|
26
31
|
const result=spawnSync(process.execPath,[path.join(ROOT,'scripts/verify-context-reconciliation'),'--root',root,safe(root,binding.context_reconciliation_ref)],{encoding:'utf8'});
|
|
27
32
|
ensure(result.status===0,`目标术语对账未通过: ${result.error?.message || result.stderr}`);
|
|
28
33
|
const context=parseContextContract({root});
|
|
@@ -66,6 +66,7 @@ export function archive(command, source, destination) {
|
|
|
66
66
|
export function sourceApprovalPolicy(source) {
|
|
67
67
|
const roles=structuredClone(source);
|
|
68
68
|
roles.user_decision_policy ||= {gates:[]};
|
|
69
|
+
ensure(!(roles.user_decision_policy.required_capabilities || []).some(id => id !== 'strategic-decision-reuse-v1'), '接收工具不支持源用户决定策略,请升级工具后验包');
|
|
69
70
|
const policy=roles.gate_policy;
|
|
70
71
|
for(const gate of policy.product_digital_human_with_biological_veto || []) {
|
|
71
72
|
if(!(policy.biological_human||[]).includes(gate) && !(policy.digital_human_review||[]).some(x=>x.gate===gate) && !(policy.dual_digital_human||[]).some(x=>x.gate===gate)) {
|
|
@@ -12,13 +12,55 @@ const MAX_BYTES = 512 * 1024 * 1024;
|
|
|
12
12
|
const own = (a,b) => JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
|
|
13
13
|
const nonempty = x => typeof x === 'string' && x.trim();
|
|
14
14
|
const bytesDigest = (bytes, kind) => kind === 'canonical-json' ? digest(parse(bytes)) : kind === 'sha256-bytes' ? hash(bytes) : (() => { throw new TypeError(`未知摘要算法: ${kind}`); })();
|
|
15
|
+
const HANDOFF_SCHEMAS = new Map([[3,'docs/process/schemas/strategic-design-handoff-v3.schema.json'],[4,'docs/process/schemas/strategic-design-handoff-v4.schema.json']]);
|
|
16
|
+
const SOURCE_SCHEMAS = new Map([
|
|
17
|
+
[3,['docs/process/schemas/strategic-handoff-domain-strategy.schema.json','docs/process/schemas/strategic-handoff-stage-decision-package.schema.json']],
|
|
18
|
+
[4,['docs/process/schemas/strategic-handoff-domain-strategy-v3.schema.json','docs/process/schemas/strategic-handoff-stage-decision-package-v3.schema.json']],
|
|
19
|
+
]);
|
|
20
|
+
const CAPABILITIES=['backend-technical-design','frontend-engineering-design','delivery-coordination'];
|
|
21
|
+
|
|
22
|
+
function versionSchema(version, schemas, label) {
|
|
23
|
+
ensure(schemas.has(version), `${label} 未知 schema_version: ${String(version)};支持版本: ${[...schemas.keys()].join(', ')};请使用显式迁移器升级`);
|
|
24
|
+
return schemas.get(version);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function validateConsumerRoutes(handoff, stage) {
|
|
28
|
+
if(handoff.schema_version!==4)return;
|
|
29
|
+
const routes=new Map();
|
|
30
|
+
for(const route of handoff.consumer_routes||[]){
|
|
31
|
+
ensure(!routes.has(route.capability),`消费者能力重复: ${route.capability}`);
|
|
32
|
+
ensure(![...routes.values()].some(item=>item.route_id===route.route_id),`消费者 route_id 重复: ${route.route_id}`);
|
|
33
|
+
routes.set(route.capability,route);
|
|
34
|
+
}
|
|
35
|
+
ensure(CAPABILITIES.every(capability=>routes.has(capability)),'Handoff v4 必须完整声明 backend、frontend 与 coordination 三条消费者路由');
|
|
36
|
+
const impact=stage.impact_assessment||{};
|
|
37
|
+
const mappingByCapability=new Map();
|
|
38
|
+
for(const mapping of stage.downstream_mapping||[]){
|
|
39
|
+
ensure(CAPABILITIES.includes(mapping.consumer_capability),`未知消费者能力: ${mapping.consumer_capability}`);
|
|
40
|
+
const values=mappingByCapability.get(mapping.consumer_capability)||[];values.push(mapping);mappingByCapability.set(mapping.consumer_capability,values);
|
|
41
|
+
}
|
|
42
|
+
const expected=(capability,required)=>required?'required':(mappingByCapability.get(capability)||[]).some(mapping=>mapping.propagation!=='not-applicable')?'optional':'not-applicable';
|
|
43
|
+
const backend=routes.get('backend-technical-design'),frontend=routes.get('frontend-engineering-design'),coordination=routes.get('delivery-coordination');
|
|
44
|
+
const backendActive=Boolean(impact.backend||impact.api||impact.data),frontendActive=Boolean(impact.frontend||impact.ui);
|
|
45
|
+
ensure(backend.activation===expected(backend.capability,backendActive),'backend 消费者路由与批准的 Backend/API/Data 影响矩阵不一致');
|
|
46
|
+
ensure(frontend.activation===expected(frontend.capability,frontendActive),'frontend 消费者路由与批准的 Frontend/UI 影响矩阵不一致');
|
|
47
|
+
ensure(coordination.activation===((backend.activation!=='not-applicable'||frontend.activation!=='not-applicable')?'required':'not-applicable'),'coordination 消费者路由与交付影响不一致');
|
|
48
|
+
const activeBackend=backend.activation!=='not-applicable';
|
|
49
|
+
ensure(own(frontend.dependencies,activeBackend?[backend.route_id]:[]),'frontend 路由依赖与 backend 路由不一致');
|
|
50
|
+
ensure(own(coordination.dependencies,[...(backend.activation!=='not-applicable'?[backend.route_id]:[]),...(frontend.activation!=='not-applicable'?[frontend.route_id]:[])]),'coordination 路由依赖与消费者路由不一致');
|
|
51
|
+
for(const route of routes.values())for(const dependency of route.dependencies)ensure([...routes.values()].some(item=>item.route_id===dependency),`消费者路由依赖悬空: ${dependency}`);
|
|
52
|
+
for(const [capability,mappings] of mappingByCapability)for(const mapping of mappings){
|
|
53
|
+
const route=routes.get(capability);
|
|
54
|
+
ensure(mapping.propagation==='not-applicable'?route.activation==='not-applicable':route.activation!=='not-applicable',`downstream mapping 与消费者路由影响不一致: ${mapping.mapping_id}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
15
57
|
|
|
16
58
|
export async function sourceApproval(record, roles, root) {
|
|
17
59
|
const options={rolesDoc:roles,requireApproved:true,root,read:ref=>readFileSync(safe(root,path.relative(root,ref).split(path.sep).join('/')))};
|
|
18
60
|
if (roles.user_decision_policy.gates.includes(record.gate_id)) {
|
|
19
61
|
ensure(existsSync(path.join(ROOT,'scripts/lib/user-decision.mjs')), '接收工具不支持源用户决定策略,请升级工具后验包');
|
|
20
|
-
const {
|
|
21
|
-
|
|
62
|
+
const { assertApprovalUserDecision } = await import('./user-decision-reuse.mjs');
|
|
63
|
+
assertApprovalUserDecision(record, roles, options);
|
|
22
64
|
}
|
|
23
65
|
validateApprovalRecord(record,options);
|
|
24
66
|
}
|
|
@@ -83,7 +125,7 @@ function previewFiles(root, config) {
|
|
|
83
125
|
|
|
84
126
|
export async function inspectSource(root, handoffRef) {
|
|
85
127
|
const handoff=read(safe(root,handoffRef));
|
|
86
|
-
schema(handoff,
|
|
128
|
+
schema(handoff,versionSchema(handoff?.schema_version,HANDOFF_SCHEMAS,'Strategic Handoff'));
|
|
87
129
|
ensure(handoff.status==='approved', '交接包尚未批准');
|
|
88
130
|
const config=handoff.package_export;
|
|
89
131
|
schema(config,'docs/process/schemas/strategic-handoff-export.schema.json');
|
|
@@ -91,13 +133,15 @@ export async function inspectSource(root, handoffRef) {
|
|
|
91
133
|
snapshot(handoff.source_context_snapshot,source); checkDelta(handoff,source);
|
|
92
134
|
const strategy=read(safe(root,handoff.source.domain_strategy_ref.persisted_ref));
|
|
93
135
|
const stage=read(safe(root,handoff.source.stage_decision_package_ref.persisted_ref));
|
|
94
|
-
|
|
136
|
+
const [strategySchema,stageSchema]=versionSchema(handoff.schema_version,SOURCE_SCHEMAS,'Strategic Handoff source');
|
|
137
|
+
schemaBatch([[strategy,strategySchema],[stage,stageSchema]]);
|
|
95
138
|
snapshot(strategy.context_snapshot,source); snapshot(stage.context_snapshot,source);
|
|
96
139
|
ensure(strategy.status==='approved' && stage.status==='approved', '战略合同/方案决策包尚未批准');
|
|
97
140
|
ensure(strategy.domain_strategy_id===handoff.source.domain_strategy_ref.id && strategy.domain_version===handoff.source.domain_strategy_ref.version, '战略身份/版本不一致');
|
|
98
141
|
ensure(stage.stage_decision_id===handoff.source.stage_decision_package_ref.id && stage.package_version===handoff.source.stage_decision_package_ref.version, '阶段包身份/版本不一致');
|
|
99
142
|
ensure(stage.domain_strategy_ref.domain_strategy_id===strategy.domain_strategy_id && stage.domain_strategy_ref.domain_version===strategy.domain_version && stage.domain_strategy_ref.status==='approved' && stage.domain_strategy_ref.persisted_ref===handoff.source.domain_strategy_ref.persisted_ref && stage.domain_strategy_ref.digest===digest(strategy), '阶段包引用的战略内容不一致');
|
|
100
143
|
ensure(!(stage.unresolved_items || []).some(x=>x.type==='blocker'), '阶段包含 blocker');
|
|
144
|
+
validateConsumerRoutes(handoff,stage);
|
|
101
145
|
const indexes=extractTraceability(strategy);
|
|
102
146
|
const sourceRoles=read(safe(root,'docs/agents/digital-human-roles.yaml'));
|
|
103
147
|
const roles=sourceApprovalPolicy(sourceRoles);
|
|
@@ -128,6 +172,19 @@ export async function inspectSource(root, handoffRef) {
|
|
|
128
172
|
ensure(record.gate_id===approval.gate_id,`批准门禁不匹配: ${key}`);
|
|
129
173
|
ensure((record.artifact_bindings || []).some(x=>x.id===(ref.id||ref.baseline_id) && x.version===ref.version && x.digest===actual),`批准记录未绑定当前资产: ${key}`);
|
|
130
174
|
}
|
|
175
|
+
if (roles.user_decision_policy.required_capabilities?.includes('strategic-decision-reuse-v1')) {
|
|
176
|
+
const record=read(safe(root,config.approvals.handoff.record_ref));
|
|
177
|
+
const scope=read(safe(root,record.subject_ref));
|
|
178
|
+
ensure(scope.kind==='strategic-delivery-scope' && scope.schema_version===1, '交接必须绑定独立的交付范围清单');
|
|
179
|
+
const {package_export,status,...delivery}=handoff;
|
|
180
|
+
ensure(scope.delivery_ref && own(read(safe(root,scope.delivery_ref)),delivery), '交付范围清单未覆盖当前交接内容或风险');
|
|
181
|
+
ensure(scope.assets?.some(asset=>asset.ref===scope.delivery_ref && asset.digest===hash(readFileSync(safe(root,scope.delivery_ref)))), '交接内容快照缺少批准覆盖');
|
|
182
|
+
for (const [key,ref] of Object.entries(handoff.source)) {
|
|
183
|
+
const file=key==='visual_baseline_ref'?`${ref.persisted_ref}/${ref.manifest_ref}`:ref.persisted_ref;
|
|
184
|
+
const decisionBoundaries=key==='business_ticket_set_ref'?['gate.spec-baseline-approved',terminalGate]:[expectedGates[key]];
|
|
185
|
+
ensure(scope.assets?.some(asset=>asset.ref===file && asset.version===ref.version && decisionBoundaries.includes(asset.boundary) && asset.digest===hash(readFileSync(safe(root,file)))), `交付范围清单遗漏当前资产: ${key}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
131
188
|
const receipt=previewFiles(root,config.prototype);
|
|
132
189
|
ensure(handoff.source.visual_baseline_ref.case_ids.every(id=>receipt.case_ids.includes(id)), '离线验证未覆盖视觉基线 case');
|
|
133
190
|
return {handoff,config,indexes,strategy,stage};
|
|
@@ -140,7 +197,7 @@ function collect(root, handoffRef, handoff, config) {
|
|
|
140
197
|
const enqueue=ref=> { if(symbolic[ref])queue.push(symbolic[ref]); else if(/^https?:\/\//i.test(ref))return; else if(ref.startsWith('evidence.'))throw new TypeError(`未解析 evidence ID: ${ref}`); else if(ref.includes('/') || /\.(?:md|yaml|json|html|png|txt|log)$/.test(ref))queue.push(ref); };
|
|
141
198
|
function refs(value,key='') {
|
|
142
199
|
if(Array.isArray(value)) { if(key==='evidence_refs') value.forEach(enqueue); else value.forEach(v=>refs(v,key)); }
|
|
143
|
-
else if(value && typeof value==='object') for(const [k,v]of Object.entries(value)) { if(['approval_ref','persisted_ref','user_decision_ref'].includes(k)&&typeof v==='string')enqueue(v); else if(k==='ref'&&typeof v==='string'&&!/^[a-z]+:\/\//i.test(v))enqueue(v); else refs(v,k); }
|
|
200
|
+
else if(value && typeof value==='object') for(const [k,v]of Object.entries(value)) { if(['approval_ref','persisted_ref','user_decision_ref','decision_reuse_ref','scope_ref','subject_ref','delivery_ref'].includes(k)&&typeof v==='string')enqueue(v); else if(k==='ref'&&typeof v==='string'&&!/^[a-z]+:\/\//i.test(v))enqueue(v); else refs(v,k); }
|
|
144
201
|
}
|
|
145
202
|
let totalBytes = 0;
|
|
146
203
|
while(queue.length) {
|
|
@@ -251,12 +308,46 @@ export async function importBundle({bundle,targetRoot}) {
|
|
|
251
308
|
mkdirSync(path.dirname(dest),{recursive:true});const stage=mkdtempSync(path.join(path.dirname(dest),'.import-staging-'));
|
|
252
309
|
try {
|
|
253
310
|
cpSync(b.root,path.join(stage,'package'),{recursive:true,errorOnExist:true,force:false});
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
311
|
+
let receipt;
|
|
312
|
+
if(b.handoff.schema_version===3){
|
|
313
|
+
receipt={schema_version:1,bundle_id:b.manifest.bundle_id,version:b.manifest.version,bundle_digest:b.manifest.bundle_digest,package_ref:`${rel}/package`,target_context_digest:context.document_digest,status:'pending-context-reconciliation'};
|
|
314
|
+
write(stage,'import-receipt.json',json(receipt));write(stage,'context-reconciliation-draft.json',json({schema_version:1,status:'draft',import_receipt_ref:`${rel}/import-receipt.json`,target_context_digest:context.document_digest,terms}));
|
|
315
|
+
write(stage,'tactical-traceability-draft.json',json({schema_version:1,import_receipt_ref:`${rel}/import-receipt.json`,bundle_digest:b.manifest.bundle_digest,rows}));
|
|
316
|
+
write(stage,'upstream-change-impact.json',json({schema_version:1,previous_bundle:b.manifest.previous_bundle,changed_source_ids:[...b.changes.updated,...b.changes.removed],added_source_ids:b.changes.added,status:'requires-lifecycle-reconciliation',policy:'mark-dependent-tactical-and-slice-contracts-stale; unknown-dependency-blocks-all'}));
|
|
317
|
+
}else{
|
|
318
|
+
const profileRef=path.join(target,'docs/process/harness-profile.yaml');
|
|
319
|
+
const profile=existsSync(profileRef)?read(profileRef):null;
|
|
320
|
+
const capabilities=profile?.handoff?.consumer_capabilities||CAPABILITIES;
|
|
321
|
+
ensure(Array.isArray(capabilities)&&capabilities.length&&capabilities.every(capability=>CAPABILITIES.includes(capability)),'目标 profile 未声明受支持的消费者能力');
|
|
322
|
+
const selected=b.handoff.consumer_routes.filter(route=>capabilities.includes(route.capability));
|
|
323
|
+
ensure(selected.length===capabilities.length,'目标 profile 的消费者能力在 Handoff v4 中缺失');
|
|
324
|
+
const commonRefs=[`${rel}/context-reconciliation-draft.json`,`${rel}/upstream-change-impact.json`];
|
|
325
|
+
const routeRecords=selected.map(route=>{
|
|
326
|
+
const artifactRefs=[...commonRefs];
|
|
327
|
+
if(route.activation!=='not-applicable'&&route.capability==='backend-technical-design')artifactRefs.push(`${rel}/backend-technical-traceability-draft.json`);
|
|
328
|
+
if(route.activation!=='not-applicable'&&route.capability==='frontend-engineering-design')artifactRefs.push(`${rel}/frontend-strategic-preflight-draft.json`,`${rel}/frontend-traceability-draft.json`);
|
|
329
|
+
if(route.activation!=='not-applicable'&&route.capability==='delivery-coordination')artifactRefs.push(`${rel}/consumer-status-index-draft.json`);
|
|
330
|
+
return {route_id:route.route_id,capability:route.capability,activation:route.activation,artifact_refs:artifactRefs};
|
|
331
|
+
});
|
|
332
|
+
receipt={schema_version:2,bundle_id:b.manifest.bundle_id,version:b.manifest.version,bundle_digest:b.manifest.bundle_digest,package_ref:`${rel}/package`,target_context_digest:context.document_digest,target_profile_id:profile?.profile_id||'yss-full-lifecycle',selected_consumer_capabilities:capabilities,routes:routeRecords,status:'pending-context-reconciliation'};
|
|
333
|
+
schema(receipt,'docs/process/schemas/strategic-handoff-import-receipt.schema.json');
|
|
334
|
+
write(stage,'import-receipt.json',json(receipt));
|
|
335
|
+
write(stage,'context-reconciliation-draft.json',json({schema_version:2,status:'draft',import_receipt_ref:`${rel}/import-receipt.json`,target_context_digest:context.document_digest,route_ids:selected.map(route=>route.route_id),terms}));
|
|
336
|
+
write(stage,'upstream-change-impact.json',json({schema_version:2,previous_bundle:b.manifest.previous_bundle,routes:selected.map(route=>({route_id:route.route_id,capability:route.capability,activation:route.activation,changed_source_ids:[...b.changes.updated,...b.changes.removed],added_source_ids:b.changes.added,status:'requires-lifecycle-reconciliation'})),policy:'mark-only-known-dependent-contracts-stale; unknown-dependency-or-design-basis-change-blocks-all'}));
|
|
337
|
+
const backendRoute=b.handoff.consumer_routes.find(route=>route.capability==='backend-technical-design');
|
|
338
|
+
const frontendRoute=b.handoff.consumer_routes.find(route=>route.capability==='frontend-engineering-design');
|
|
339
|
+
const coordinationRoute=b.handoff.consumer_routes.find(route=>route.capability==='delivery-coordination');
|
|
340
|
+
if(capabilities.includes('backend-technical-design')&&backendRoute.activation!=='not-applicable')write(stage,'backend-technical-traceability-draft.json',json({schema_version:2,route_id:backendRoute.route_id,import_receipt_ref:`${rel}/import-receipt.json`,bundle_digest:b.manifest.bundle_digest,rows}));
|
|
341
|
+
if(capabilities.includes('frontend-engineering-design')&&frontendRoute.activation!=='not-applicable'){
|
|
342
|
+
const preflight={schema_version:1,status:'draft',route_id:frontendRoute.route_id,capability:frontendRoute.capability,import_receipt_ref:`${rel}/import-receipt.json`,bundle_digest:b.manifest.bundle_digest,context_reconciliation_ref:'',visual_baseline_ref:`${rel}/package/payload/files/${b.handoff.source.visual_baseline_ref.persisted_ref}/${b.handoff.source.visual_baseline_ref.manifest_ref}`,source_rule_refs:rows.map(row=>row.source_id),backend_dependency:{mode:backendRoute.activation==='not-applicable'?'not-applicable':'required',route_id:backendRoute.route_id,...(backendRoute.activation==='not-applicable'?{reason:backendRoute.reason,impact_refs:backendRoute.impact_refs,evidence_refs:backendRoute.evidence_refs}:{})},ready_for_agent:false};
|
|
343
|
+
schema(preflight,'docs/process/schemas/frontend-strategic-preflight.schema.json');
|
|
344
|
+
write(stage,'frontend-strategic-preflight-draft.json',json(preflight));write(stage,'frontend-traceability-draft.json',json({schema_version:1,route_id:frontendRoute.route_id,import_receipt_ref:`${rel}/import-receipt.json`,bundle_digest:b.manifest.bundle_digest,rows:rows.map(({tactical_refs,test_seam_refs,...row})=>({...row,frontend_case_refs:[]}))}));
|
|
345
|
+
}
|
|
346
|
+
if(capabilities.includes('delivery-coordination')&&coordinationRoute.activation!=='not-applicable')write(stage,'consumer-status-index-draft.json',json({schema_version:1,status:'draft',import_receipt_ref:`${rel}/import-receipt.json`,routes:b.handoff.consumer_routes.map(route=>({route_id:route.route_id,capability:route.capability,activation:route.activation,consumer_status:route.activation==='not-applicable'?'not-applicable':'pending',receipt_refs:[],acceptance_refs:[],feedback_refs:[]}))}));
|
|
347
|
+
}
|
|
258
348
|
ensure(!existsSync(dest),'导入并发冲突');renameSync(stage,dest);
|
|
259
|
-
|
|
349
|
+
const traceabilityRef=b.handoff.schema_version===3?`${rel}/tactical-traceability-draft.json`:receipt.routes.find(route=>route.capability==='backend-technical-design')?.artifact_refs.find(ref=>ref.endsWith('backend-technical-traceability-draft.json'))||receipt.routes.find(route=>route.capability==='frontend-engineering-design')?.artifact_refs.find(ref=>ref.endsWith('frontend-traceability-draft.json'));
|
|
350
|
+
return {result:'imported-pending-reconciliation',receipt_ref:`${rel}/import-receipt.json`,...(traceabilityRef?{traceability_ref:traceabilityRef}:{})};
|
|
260
351
|
}finally{rmSync(stage,{recursive:true,force:true});}
|
|
261
352
|
});
|
|
262
353
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { assertUserDecisionRequirement, decisionIO, decisionDigest, decisionError } from './user-decision.mjs';
|
|
2
|
+
|
|
3
|
+
export const DECISION_REUSE_CAPABILITY = 'strategic-decision-reuse-v1';
|
|
4
|
+
const fail = detail => decisionError('user-decision-reuse-invalid', detail);
|
|
5
|
+
const sameSet = (a,b) => Array.isArray(a) && Array.isArray(b) && a.length === new Set(a).size && b.length === new Set(b).size && JSON.stringify([...a].sort()) === JSON.stringify([...b].sort());
|
|
6
|
+
|
|
7
|
+
// A reuse record is a coverage proof, never a new reply or a substitute for one.
|
|
8
|
+
export function assertApprovalUserDecision(record, roles, options = {}) {
|
|
9
|
+
const policy = roles.user_decision_policy;
|
|
10
|
+
const io = decisionIO(options);
|
|
11
|
+
if (policy.required_capabilities?.some(id => id !== DECISION_REUSE_CAPABILITY)) fail('接收工具不支持源用户决定策略,请升级');
|
|
12
|
+
if (!record.decision_reuse_ref) return assertUserDecisionRequirement({boundary:record.gate_id,subject_ref:record.subject_ref,scope:record.approval_scope,user_decision_ref:record.user_decision_ref}, {...options,rolesDoc:roles});
|
|
13
|
+
if (record.gate_id !== 'gate.strategic-design-handoff-approved' || !policy.required_capabilities?.includes(DECISION_REUSE_CAPABILITY)) fail('未登记的复用边界或能力');
|
|
14
|
+
const reuse = io.document(record.decision_reuse_ref);
|
|
15
|
+
if (reuse.schema_version !== 1 || reuse.kind !== DECISION_REUSE_CAPABILITY || reuse.target_gate !== record.gate_id || reuse.scope_ref !== record.subject_ref || !sameSet(reuse.scope, record.approval_scope)) fail('目标边界、资产或范围不匹配');
|
|
16
|
+
const manifest = io.document(reuse.scope_ref);
|
|
17
|
+
if (manifest.schema_version !== 1 || manifest.kind !== 'strategic-delivery-scope' || !Array.isArray(manifest.assets) || !manifest.assets.length || !Array.isArray(manifest.risks) || !Array.isArray(manifest.conditions)) fail('交付范围清单不完整');
|
|
18
|
+
if (!Array.isArray(reuse.requirements) || !reuse.requirements.length) fail('缺少原决定');
|
|
19
|
+
const approved = [];
|
|
20
|
+
for (const requirement of reuse.requirements) {
|
|
21
|
+
const result = assertUserDecisionRequirement(requirement,{...options,rolesDoc:roles});
|
|
22
|
+
const source = io.document(requirement.user_decision_ref);
|
|
23
|
+
for (const item of source.request.items.filter(item => result.validated.some(v => v.item_id === item.id))) approved.push({item, requirement});
|
|
24
|
+
}
|
|
25
|
+
const identities = new Set();
|
|
26
|
+
for (const asset of manifest.assets) {
|
|
27
|
+
if (!asset.ref || !asset.version || !asset.boundary || !Array.isArray(asset.scope) || !asset.scope.length || identities.has(`${asset.boundary}:${asset.ref}`)) fail('资产缺少身份、专业边界、范围或重复');
|
|
28
|
+
identities.add(`${asset.boundary}:${asset.ref}`);
|
|
29
|
+
if (decisionDigest(io.bytes(asset.ref)) !== asset.digest) fail(`资产已变化: ${asset.ref}`);
|
|
30
|
+
const covering = approved.filter(({item,requirement}) => item.boundary === asset.boundary && asset.scope.every(s => requirement.scope.includes(s)) && [item.subject,...item.basis].some(a => a.ref === asset.ref && a.version === asset.version && a.digest === asset.digest));
|
|
31
|
+
if (!covering.length) fail(`没有原决定覆盖: ${asset.ref} / ${asset.boundary}`);
|
|
32
|
+
}
|
|
33
|
+
const coveredScope = new Set(manifest.assets.flatMap(asset => asset.scope));
|
|
34
|
+
if (!reuse.scope.length || reuse.scope.some(s => !coveredScope.has(s))) fail('交付范围超出资产批准范围');
|
|
35
|
+
for (const risk of manifest.risks) if (!approved.some(({item}) => item.risks.includes(risk))) fail(`新增未接受风险: ${risk}`);
|
|
36
|
+
for (const condition of manifest.conditions) if (!approved.some(({item}) => item.next_actions.includes(condition))) fail(`新增授权条件: ${condition}`);
|
|
37
|
+
return {reused:true,manifest,validated:approved.map(({item})=>({item_id:item.id,principal_ref:item.responder_ref}))};
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { verifyConsumerFeedback, verifyFeedbackAdjudication } from './lib/strategic-feedback.mjs';
|
|
4
|
+
|
|
5
|
+
try{
|
|
6
|
+
const [command,...args]=process.argv.slice(2);const {values,positionals}=parseArgs({args,options:{root:{type:'string',default:process.cwd()}},allowPositionals:true,strict:true});
|
|
7
|
+
if(positionals.length!==1||!['verify-feedback','verify-adjudication'].includes(command))throw new TypeError('用法: scripts/strategic-feedback <verify-feedback|verify-adjudication> --root <target> <contract.json>');
|
|
8
|
+
const result=command==='verify-feedback'?await verifyConsumerFeedback({root:values.root,feedbackRef:positionals[0]}):await verifyFeedbackAdjudication({root:values.root,adjudicationRef:positionals[0]});
|
|
9
|
+
process.stdout.write(`${JSON.stringify(result,null,2)}\n`);
|
|
10
|
+
}catch(error){process.stderr.write(`${error.message}\n`);process.exitCode=1;}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { verifyFrontendStrategicPreflight } from './lib/frontend-delivery.mjs';
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const {values,positionals}=parseArgs({options:{root:{type:'string',default:process.cwd()},'expected-digest':{type:'string'}},allowPositionals:true,strict:true});
|
|
7
|
+
if(positionals.length!==1)throw new TypeError('用法: scripts/verify-frontend-strategic-preflight [--root <frontend>] [--expected-digest <sha256>] <preflight.json>');
|
|
8
|
+
const result=await verifyFrontendStrategicPreflight({root:values.root,preflightRef:positionals[0],expectedDigest:values['expected-digest']});
|
|
9
|
+
process.stdout.write(`${JSON.stringify(result,null,2)}\n`);
|
|
10
|
+
}catch(error){
|
|
11
|
+
process.stderr.write(`${error.message}\n`);process.exitCode=1;
|
|
12
|
+
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"revision": "c4d958f18cc947cb91cbe1bd48783fa4601cc18f"
|
|
19
19
|
},
|
|
20
20
|
"iloveZzz/yss-harness-design-agent": {
|
|
21
|
-
"revision": "
|
|
21
|
+
"revision": "e24465921a8a51d8690a1732842274a35b935c9e"
|
|
22
22
|
},
|
|
23
23
|
"ayghri/i-have-adhd": {
|
|
24
24
|
"revision": "7b9069b39972e269e61bd95c2f66ebb90cac6a02"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"source": "tt-a1i/archify",
|
|
43
43
|
"sourceType": "github",
|
|
44
44
|
"skillPath": "archify/SKILL.md",
|
|
45
|
-
"effectiveHash": "
|
|
45
|
+
"effectiveHash": "f7e3f1643070db1537ad4dfe7f5718c5d90033b27c12cce1762720724ed0e356",
|
|
46
46
|
"sourceRevision": "199360cc6687a7857b54dd188d4922b09e466a4b",
|
|
47
47
|
"upstreamHash": "a04c5666d6a5f46bc6f220a36b851e96aaa69ff0630049631364fb23fa5b2ee5",
|
|
48
48
|
"adaptationRef": "docs/agents/archify-integration.md",
|
|
@@ -481,7 +481,7 @@
|
|
|
481
481
|
"sourceType": "git-submodule",
|
|
482
482
|
"skillPath": ".agents/skills/prototype-review/SKILL.md",
|
|
483
483
|
"effectiveHash": "df6f131bef0f52a96d307d98ab42d74888a9b72c5508911b5622913b334e8eab",
|
|
484
|
-
"sourceRevision": "
|
|
484
|
+
"sourceRevision": "e24465921a8a51d8690a1732842274a35b935c9e",
|
|
485
485
|
"upstreamHash": "9206df8b6c555c1af1351e425c5ec99ec2a40263c6861607f1418b9c520873ab",
|
|
486
486
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
487
487
|
"targets": [
|
|
@@ -701,7 +701,7 @@
|
|
|
701
701
|
"sourceType": "git-submodule",
|
|
702
702
|
"skillPath": ".agents/skills/yss-antd-design/SKILL.md",
|
|
703
703
|
"effectiveHash": "92739eb34352fd70fbb52f4d9d6c6ac09aa02bc0155a4bd812a98cd5f933ff70",
|
|
704
|
-
"sourceRevision": "
|
|
704
|
+
"sourceRevision": "e24465921a8a51d8690a1732842274a35b935c9e",
|
|
705
705
|
"upstreamHash": "08ce99d45586d5ea19e25fdd9e9a6ede16e66f18992b70b87e0cd23f92343b16",
|
|
706
706
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
707
707
|
"targets": [
|
|
@@ -716,7 +716,7 @@
|
|
|
716
716
|
"sourceType": "git-submodule",
|
|
717
717
|
"skillPath": ".agents/skills/yss-antdv-next-design/SKILL.md",
|
|
718
718
|
"effectiveHash": "c7c07c5dc3716f578904559473d43d92878aa25a6a45c8827e288f0242d42b75",
|
|
719
|
-
"sourceRevision": "
|
|
719
|
+
"sourceRevision": "e24465921a8a51d8690a1732842274a35b935c9e",
|
|
720
720
|
"upstreamHash": "2942ed52dd58a38e51f09eb11254809334aeaed08f21cd081f09f35e6647a68c",
|
|
721
721
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
722
722
|
"targets": [
|
|
@@ -806,7 +806,7 @@
|
|
|
806
806
|
"sourceType": "git-submodule",
|
|
807
807
|
"skillPath": ".agents/skills/yss-design-system/SKILL.md",
|
|
808
808
|
"effectiveHash": "e69597b8b6a5ce4de5bd40f67630443b8176202f6b028b2ce146a2a1d062816f",
|
|
809
|
-
"sourceRevision": "
|
|
809
|
+
"sourceRevision": "e24465921a8a51d8690a1732842274a35b935c9e",
|
|
810
810
|
"upstreamHash": "90aaf06b2514353152464a4d032076fa9d5ef56272d8ad526f5544f3c22a2b00",
|
|
811
811
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
812
812
|
"targets": [
|
|
@@ -1040,7 +1040,7 @@
|
|
|
1040
1040
|
"sourceType": "git-submodule",
|
|
1041
1041
|
"skillPath": ".agents/skills/yss-prototype-stage/SKILL.md",
|
|
1042
1042
|
"effectiveHash": "c87c7c38c9598c04f28cfa759fd6c1c12b2937b49e92f8171992d8346cb179e0",
|
|
1043
|
-
"sourceRevision": "
|
|
1043
|
+
"sourceRevision": "e24465921a8a51d8690a1732842274a35b935c9e",
|
|
1044
1044
|
"upstreamHash": "aa78ef758ad3c515e94fb6b87d4e519f39a38e53bb3c82413d707f7d0f96f58b",
|
|
1045
1045
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
1046
1046
|
"targets": [
|
|
@@ -1117,9 +1117,9 @@
|
|
|
1117
1117
|
"source": "iloveZzz/yss-harness-design-agent",
|
|
1118
1118
|
"sourceType": "git-submodule",
|
|
1119
1119
|
"skillPath": ".agents/skills/yss-stage-decision/SKILL.md",
|
|
1120
|
-
"effectiveHash": "
|
|
1121
|
-
"sourceRevision": "
|
|
1122
|
-
"upstreamHash": "
|
|
1120
|
+
"effectiveHash": "ada584c7b996439a47e24153e156c38198f60f3f84250cd37aea6e2a118131aa",
|
|
1121
|
+
"sourceRevision": "e24465921a8a51d8690a1732842274a35b935c9e",
|
|
1122
|
+
"upstreamHash": "8752a1ed6903bd20c33e21b521d52bd4f7a0662b1d25e00152b60ba38333c93b",
|
|
1123
1123
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
1124
1124
|
"targets": [
|
|
1125
1125
|
".agents/skills",
|
package/template.manifest.json
CHANGED
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"scripts/verify-retired-skill-ids",
|
|
83
83
|
"scripts/verify-skill-governance",
|
|
84
84
|
"scripts/verify-skill-registry",
|
|
85
|
+
"scripts/verify-strategic-handoff-tools-lock",
|
|
85
86
|
"scripts/verify-template",
|
|
86
87
|
"scripts/verify-template-candidate",
|
|
87
88
|
"scripts/verify-template-fast",
|
package/template.snapshot.json
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
"templateName": "yss-spec-project-template",
|
|
4
4
|
"templateSource": "github:iloveZzz/yss-spec-project-template",
|
|
5
5
|
"templateRepository": "https://github.com/iloveZzz/yss-spec-project-template.git",
|
|
6
|
-
"requestedRef": "
|
|
7
|
-
"templateCommit": "
|
|
8
|
-
"
|
|
6
|
+
"requestedRef": "working-tree",
|
|
7
|
+
"templateCommit": "32fa044c0123f73b8d8091590d1f060440ac9ccb",
|
|
8
|
+
"sourceState": "working-tree",
|
|
9
|
+
"manifestHash": "191240f4716de5f93cf1b30ea917a9a14dfd4a29b12dd2ba569c6271fa46009e",
|
|
9
10
|
"encodedPaths": {
|
|
10
11
|
".codex/skills/data-analytics/.gitignore": ".codex/skills/data-analytics/__yss_dotfile__.gitignore",
|
|
11
12
|
".codex/skills/product-design/.npmignore": ".codex/skills/product-design/__yss_dotfile__.npmignore",
|
|
12
13
|
".codex/skills/product-design/templates/prototype/.npmrc": ".codex/skills/product-design/templates/prototype/__yss_dotfile__.npmrc",
|
|
13
14
|
".gitignore": "__yss_dotfile__.gitignore"
|
|
14
15
|
},
|
|
15
|
-
"snapshotHash": "
|
|
16
|
-
"generatedAt": "2026-09-
|
|
16
|
+
"snapshotHash": "c6c4ab647dbf5a99159098417f05edcd7133c2907fe00aeaae0ea06d5589bf0d",
|
|
17
|
+
"generatedAt": "2026-09-11T13:22:12.721Z"
|
|
17
18
|
}
|