create-yss-spec 3.4.9 → 3.5.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/README.md +2 -2
- package/package.json +1 -1
- package/src/api/_sync-service.js +111 -18
- package/src/cli/args.js +1 -0
- package/src/cli/help.js +10 -1
- package/src/cli/index.js +3 -0
- package/src/cli/prompts.js +5 -1
- package/src/cli/router.js +2 -0
- package/src/commands/attach.js +12 -2
- package/src/commands/init.js +2 -0
- package/src/commands/skills.js +253 -0
- package/src/commands/sync.js +3 -0
- package/src/family-identity.js +1 -1
- package/src/template/asset-runtime.js +220 -0
- package/src/template/attach-planner.js +5 -0
- package/src/template/distribution-runtime.js +165 -0
- package/src/template/instance-runtime.js +76 -29
- package/src/template/ownership-policy.js +17 -1
- package/src/template/prune-planner.js +6 -1
- package/src/template/sync-planner.js +8 -0
- package/src/template/verification-runtime.js +56 -1
- package/src/validation/metadata.js +24 -1
- package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
- package/template/.agents/skills/yss-cache/SKILL.md +6 -5
- package/template/.agents/skills/yss-cache/references/annotations.md +3 -1
- package/template/.agents/skills/yss-cache/references/architecture.md +4 -2
- package/template/.agents/skills/yss-cache/references/configuration.md +6 -3
- package/template/.agents/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.agents/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.agents/skills/yss-cache/references/verification.md +10 -4
- package/template/.agents/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.agents/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.agents/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.agents/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.agents/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.agents/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.agents/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.agents/skills/yss-prototype-stage/SKILL.md +4 -2
- package/template/.agents/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.agents/skills/yss-repository/SKILL.md +1 -0
- package/template/.agents/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.agents/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.agents/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.agents/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.agents/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/.codex/skills/yss-cache/SKILL.md +6 -5
- package/template/.codex/skills/yss-cache/references/annotations.md +3 -1
- package/template/.codex/skills/yss-cache/references/architecture.md +4 -2
- package/template/.codex/skills/yss-cache/references/configuration.md +6 -3
- package/template/.codex/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.codex/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.codex/skills/yss-cache/references/verification.md +10 -4
- package/template/.codex/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.codex/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.codex/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.codex/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.codex/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.codex/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.codex/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.codex/skills/yss-prototype-stage/SKILL.md +4 -2
- package/template/.codex/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.codex/skills/yss-repository/SKILL.md +1 -0
- package/template/.codex/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.codex/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.codex/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.codex/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.codex/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/.cursor/skills/yss-cache/SKILL.md +6 -5
- package/template/.cursor/skills/yss-cache/references/annotations.md +3 -1
- package/template/.cursor/skills/yss-cache/references/architecture.md +4 -2
- package/template/.cursor/skills/yss-cache/references/configuration.md +6 -3
- package/template/.cursor/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.cursor/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.cursor/skills/yss-cache/references/verification.md +10 -4
- package/template/.cursor/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.cursor/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.cursor/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.cursor/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.cursor/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.cursor/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.cursor/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.cursor/skills/yss-prototype-stage/SKILL.md +4 -2
- package/template/.cursor/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.cursor/skills/yss-repository/SKILL.md +1 -0
- package/template/.cursor/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.cursor/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.cursor/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.cursor/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.cursor/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/.pi/skills/yss-cache/SKILL.md +6 -5
- package/template/.pi/skills/yss-cache/references/annotations.md +3 -1
- package/template/.pi/skills/yss-cache/references/architecture.md +4 -2
- package/template/.pi/skills/yss-cache/references/configuration.md +6 -3
- package/template/.pi/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.pi/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.pi/skills/yss-cache/references/verification.md +10 -4
- package/template/.pi/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.pi/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.pi/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.pi/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.pi/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.pi/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.pi/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.pi/skills/yss-prototype-stage/SKILL.md +4 -2
- package/template/.pi/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.pi/skills/yss-repository/SKILL.md +1 -0
- package/template/.pi/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.pi/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.pi/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.pi/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.pi/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/AGENTS.md +2 -0
- package/template/CONTEXT.md +2 -1
- package/template/README.md +3 -5
- package/template/docs/agents/issue-tracker.md +7 -0
- package/template/docs/agents/skill-migrations.md +4 -0
- package/template/docs/agents/yss-skill-registry.yaml +7 -6
- package/template/docs/design/README.md +0 -1
- package/template/docs/design/design.md +2 -2
- package/template/docs/design/templates/interaction-spec-template.md +1 -1
- package/template/docs/engineering/evidence/aliyun-artifact-resolution.json +446 -0
- package/template/docs/process/contract-reading.md +46 -0
- package/template/docs/process/lifecycle-artifact-map.md +2 -2
- package/template/docs/process/lifecycle-registry-baseline.json +2 -1
- package/template/docs/process/lifecycle-registry.yaml +10 -4
- package/template/docs/process/schemas/api-contract-decision-v2.schema.json +198 -0
- package/template/docs/process/schemas/digital-human-task-package.schema.json +8 -0
- package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +259 -0
- package/template/docs/process/schemas/stage-tracking.schema.json +259 -0
- package/template/docs/process/stage-tracking.md +62 -0
- package/template/docs/process/strategic-handoff-package.md +8 -0
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +2 -0
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214/347/264/242/345/274/225.md +1 -0
- package/template/docs/user-guide//351/230/266/346/256/265/346/216/245/345/205/245/344/270/216/344/272/247/347/211/251/345/244/215/347/224/250/344/275/277/347/224/250/346/211/213/345/206/214.md +243 -0
- package/template/scripts/api-contract-decision +16 -0
- package/template/scripts/complete-backend-delivery +17 -0
- package/template/scripts/contract +13 -0
- package/template/scripts/design-md +2 -0
- package/template/scripts/dispatch-slice-task +13 -0
- package/template/scripts/lib/api-contract-decision.mjs +45 -4
- package/template/scripts/lib/approval-record.mjs +1 -1
- package/template/scripts/lib/approved-execution-context.mjs +35 -7
- package/template/scripts/lib/backend-architecture.mjs +2 -2
- package/template/scripts/lib/backend-delivery-terminal.mjs +51 -0
- package/template/scripts/lib/backend-platform-provenance.mjs +1 -1
- package/template/scripts/lib/backend-platform.mjs +1 -1
- package/template/scripts/lib/backend-scaffold-prerequisites.mjs +1 -1
- package/template/scripts/lib/context-contract.mjs +1 -1
- package/template/scripts/lib/context-reconciliation.mjs +1 -1
- package/template/scripts/lib/contract-source.mjs +22 -0
- package/template/scripts/lib/contract-views.mjs +109 -0
- package/template/scripts/lib/delivery-preflight.mjs +3 -2
- package/template/scripts/lib/design-md.mjs +230 -0
- package/template/scripts/lib/digital-human-roles.mjs +1 -1
- package/template/scripts/lib/existing-backend-architecture.mjs +4 -3
- package/template/scripts/lib/existing-ui-baseline.mjs +4 -2
- package/template/scripts/lib/frontend-delivery-boundary.mjs +1 -1
- package/template/scripts/lib/frontend-delivery.mjs +1 -1
- package/template/scripts/lib/harness-execution-scope.mjs +15 -1
- package/template/scripts/lib/implementation-contract-compiler.mjs +4 -4
- package/template/scripts/lib/json-schema.mjs +19 -4
- package/template/scripts/lib/lifecycle-context-query.mjs +5 -1
- package/template/scripts/lib/lifecycle-controls.mjs +1 -1
- package/template/scripts/lib/lifecycle-execution-scope.mjs +92 -0
- package/template/scripts/lib/lifecycle-registry.mjs +2 -1
- package/template/scripts/lib/lifecycle-transition.mjs +21 -3
- package/template/scripts/lib/openapi-draft-validation.mjs +2 -1
- package/template/scripts/lib/skill-registry.mjs +10 -3
- package/template/scripts/lib/skill-supply-chain.mjs +33 -14
- package/template/scripts/lib/slice-contract-preparation.mjs +16 -14
- package/template/scripts/lib/slice-contract-views.mjs +1 -1
- package/template/scripts/lib/slice-contract.mjs +18 -3
- package/template/scripts/lib/slice-execution-preflight.mjs +7 -3
- package/template/scripts/lib/slice-repositories.mjs +1 -1
- package/template/scripts/lib/slice-task-package.mjs +3 -2
- package/template/scripts/lib/stage-tracking-migration.mjs +176 -0
- package/template/scripts/lib/stage-tracking.mjs +184 -0
- package/template/scripts/lib/strategic-handoff-consumption.mjs +5 -4
- package/template/scripts/lib/strategic-handoff-io.mjs +5 -2
- package/template/scripts/lib/strategic-handoff-routing.mjs +31 -0
- package/template/scripts/lib/strategic-handoff.mjs +4 -2
- package/template/scripts/lib/task-package.mjs +2 -0
- package/template/scripts/lib/technical-design-boundary.mjs +4 -2
- package/template/scripts/lib/ui-baseline.mjs +1 -1
- package/template/scripts/lib/user-decision.mjs +1 -1
- package/template/scripts/lib/validation-phase.mjs +72 -0
- package/template/scripts/stage-tracking +25 -0
- package/template/scripts/strategic-consumer-entry +47 -0
- package/template/scripts/verify-context-reconciliation +4 -3
- package/template/scripts/verify-lifecycle-checkpoint +10 -0
- package/template/scripts/verify-project-instance +11 -10
- package/template/skills-lock.json +17 -32
- package/template.manifest.json +18 -3
- package/template.snapshot.json +5 -5
- package/template/.agents/skills/grill-me/SKILL.md +0 -7
- package/template/.agents/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.agents/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.agents/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.agents/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/.codex/skills/grill-me/SKILL.md +0 -7
- package/template/.codex/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.codex/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.codex/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.codex/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/.cursor/skills/grill-me/SKILL.md +0 -7
- package/template/.cursor/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.cursor/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.cursor/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.cursor/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/.pi/skills/grill-me/SKILL.md +0 -7
- package/template/.pi/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.pi/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.pi/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.pi/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/docs/agents/yss-plugin-dependency-contract.md +0 -45
- package/template/docs/api/.gitkeep +0 -0
- package/template/docs/api/specs/.gitkeep +0 -0
- package/template/docs/architecture/.gitkeep +0 -0
- package/template/docs/design/facts/antdv-next/1.5.2/cli-help.txt +0 -38
- package/template/docs/design/facts/antdv-next/1.5.2/component-list.json +0 -434
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/info.json +0 -246
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/semantic.json +0 -40
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/token.json +0 -32
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/info.json +0 -190
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/semantic.json +0 -20
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/token.json +0 -256
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/info.json +0 -460
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/semantic.json +0 -70
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/token.json +0 -123
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/info.json +0 -646
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/semantic.json +0 -50
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/token.json +0 -130
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/info.json +0 -640
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/semantic.json +0 -70
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/token.json +0 -172
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/info.json +0 -1010
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/semantic.json +0 -70
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/token.json +0 -263
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/info.json +0 -321
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/semantic.json +0 -25
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/token.json +0 -25
- package/template/docs/design/facts/antdv-next/1.5.2/design-md.json +0 -3
- package/template/docs/design/facts/antdv-next/1.5.2/manifest.json +0 -215
- package/template/docs/design/facts/antdv-next/1.5.2/resolution-probe.json +0 -5
- package/template/docs/design/prototypes/.gitkeep +0 -1
- package/template/docs/implementation/.gitkeep +0 -1
- package/template/docs/plan/IDEATION.md +0 -96
- package/template/docs/process/PDCA-SCRUM.md +0 -10
- package/template/docs/process/harness-executive-blueprint.md +0 -10
- package/template/docs/releases/.gitkeep +0 -1
- package/template/docs/requirements/README.md +0 -85
- package/template/docs/requirements/tickets/.gitkeep +0 -1
- package/template/docs/templates/agent-brief-template.md +0 -46
- package/template/docs/templates/architecture-proposal-template.md +0 -18
- package/template/docs/templates/implementation-plan-template.md +0 -16
- package/template/docs/templates/implementation-routing-template.md +0 -350
- package/template/docs/templates/requirement-freeze-template.md +0 -63
- package/template/docs/templates/risk-register-template.md +0 -11
- package/template/docs/templates/user-story-template.md +0 -20
- package/template/docs/testing/README.md +0 -110
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { parseSliceYaml } from './lib/slice-contract.mjs';
|
|
6
|
+
import { compileSliceTaskPackage } from './lib/slice-task-package.mjs';
|
|
7
|
+
try {
|
|
8
|
+
const { values } = parseArgs({ options: { root: { type: 'string' }, input: { type: 'string' } } });
|
|
9
|
+
if (!values.root || !values.input) throw new TypeError('用法: dispatch-slice-task --root <project> --input <task-input.yaml>');
|
|
10
|
+
const input = parseSliceYaml(readFileSync(values.input));
|
|
11
|
+
const result = compileSliceTaskPackage(input.binding, { ...input, root: path.resolve(values.root) });
|
|
12
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
13
|
+
} catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {readContractSource} from './contract-source.mjs';
|
|
2
|
+
import { readFileSync, withValidationPhase } from "./validation-phase.mjs";
|
|
3
|
+
import { parseSliceYaml } from "./slice-contract.mjs";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
@@ -15,15 +17,18 @@ const readBound = (root, ref, label) => {
|
|
|
15
17
|
};
|
|
16
18
|
const requireDigest = (asset, expected) => { if (asset.digest !== expected) fail(`${asset.label}摘要漂移`); };
|
|
17
19
|
|
|
18
|
-
export function validateApiContractDecision(binding,
|
|
20
|
+
export function validateApiContractDecision(binding, options = {}) {return withValidationPhase({root:options.root||TEMPLATE_ROOT,purpose:'api-decision',slice_id:binding?.ref,readOnly:true},()=>validate(binding,options));}
|
|
21
|
+
function validate(binding, { root = TEMPLATE_ROOT } = {}) {
|
|
19
22
|
if (!binding || !binding.ref || !binding.version || !binding.digest || !["required", "not-applicable"].includes(binding.impact)) fail("缺少 API Contract Decision 当前版本绑定");
|
|
20
23
|
const projectRoot = path.resolve(root);
|
|
21
24
|
const decisionAsset = readBound(projectRoot, binding.ref, "API Contract Decision");
|
|
22
25
|
requireDigest(decisionAsset, binding.digest);
|
|
23
26
|
const document = parseDocument(decisionAsset.bytes.toString("utf8"), { uniqueKeys: true, maxAliasCount: 0 });
|
|
24
27
|
if (document.errors.length) fail(`API Contract Decision 无法解析: ${document.errors[0].message}`);
|
|
25
|
-
const
|
|
26
|
-
validateJsonSchema(
|
|
28
|
+
const original = parseSliceYaml(decisionAsset.bytes);
|
|
29
|
+
validateJsonSchema(original, original.schema_version===2?SCHEMA.replace(".schema", "-v2.schema"):SCHEMA, { cwd: TEMPLATE_ROOT, label: "API Contract Decision" });
|
|
30
|
+
if(original.status!=="approved")fail("API Contract Decision 必须经生命周期批准为 approved");
|
|
31
|
+
const decision={...structuredClone(original),current_version:true}; // Derived only after schema and current raw binding checks; never persisted.
|
|
27
32
|
if (decision.decision_version !== binding.version || decision.impact !== binding.impact) fail("API Contract Decision 版本或影响结论与合同不一致");
|
|
28
33
|
requireDigest(readBound(projectRoot, decision.assessment_ref, "API 影响评估"), decision.assessment_digest);
|
|
29
34
|
for (const ref of decision.evidence_refs) readBound(projectRoot, ref, "API Decision 证据");
|
|
@@ -40,6 +45,12 @@ export function validateApiContractDecision(binding, { root = TEMPLATE_ROOT } =
|
|
|
40
45
|
const reviewRecord = reviewDocument.toJS({ maxAliasCount: 0 });
|
|
41
46
|
if (reviewRecord?.result !== "approved" || !Array.isArray(reviewRecord.blocking_findings) || reviewRecord.blocking_findings.length > 0) fail("OpenAPI Draft Review 实际记录未批准或仍有阻断项");
|
|
42
47
|
if (reviewRecord.draft_ref !== decision.openapi.ref || reviewRecord.draft_digest !== decision.openapi.digest) fail("OpenAPI Draft Review 实际记录未绑定当前权威 YAML");
|
|
48
|
+
if (!Array.isArray(reviewRecord.evidence_refs) || !reviewRecord.evidence_refs.length) fail("OpenAPI Draft Review 缺少实际证据");
|
|
49
|
+
for (const ref of reviewRecord.evidence_refs) readBound(projectRoot, ref, "OpenAPI Draft Review 实际证据");
|
|
50
|
+
if(original.schema_version===2){
|
|
51
|
+
decision.draft_review={...decision.draft_review,result:reviewRecord.result,blocking_findings:reviewRecord.blocking_findings,draft_ref:reviewRecord.draft_ref,draft_digest:reviewRecord.draft_digest,evidence_refs:reviewRecord.evidence_refs};
|
|
52
|
+
decision.freeze={...decision.freeze,draft_ref:decision.openapi.ref,draft_digest:decision.openapi.digest};
|
|
53
|
+
}
|
|
43
54
|
for (const ref of decision.draft_review.evidence_refs) readBound(projectRoot, ref, "OpenAPI Draft Review 证据");
|
|
44
55
|
const validated = validateOpenApiDraftValidationRecord(validation.file, { root: projectRoot });
|
|
45
56
|
if (validated.record.draft.ref !== decision.openapi.ref || validated.draftDigest !== decision.openapi.digest) fail("OpenAPI Validation 未绑定当前权威 YAML");
|
|
@@ -47,3 +58,33 @@ export function validateApiContractDecision(binding, { root = TEMPLATE_ROOT } =
|
|
|
47
58
|
if (decision.freeze.version !== decision.openapi.version || decision.freeze.draft_ref !== decision.openapi.ref || decision.freeze.draft_digest !== decision.openapi.digest) fail("OpenAPI Freeze 未绑定当前权威 YAML 版本和摘要");
|
|
48
59
|
return { decision, openapi: decision.openapi, binding: { ref: binding.ref, version: binding.version, digest: binding.digest, impact: decision.impact } };
|
|
49
60
|
}
|
|
61
|
+
|
|
62
|
+
/** Source selectors contain business choices; digests are always derived from original bytes. */
|
|
63
|
+
export function prepareApiContractDecision(input,{root=TEMPLATE_ROOT}={}) {
|
|
64
|
+
return withValidationPhase({root,purpose:'api-prepare',slice_id:input.decision_id,readOnly:true},()=>{
|
|
65
|
+
if(input.schema_version!==undefined&&input.schema_version!==2)fail('旧 schema 必须通过显式 migrate 迁移');
|
|
66
|
+
if(input.kind!==undefined&&input.kind!=='api-contract-decision')fail('来源 kind 与 API 决定冲突');
|
|
67
|
+
const allowed=new Set(['schema_version','kind','decision_id','decision_version','status','impact','assessment_ref','assessment_digest','evidence_refs','reason','openapi','validation_record','draft_review','freeze']);
|
|
68
|
+
for(const key of Object.keys(input))if(!allowed.has(key))fail(`未分类约束需人工处理: ${key}`);
|
|
69
|
+
if(input.status&&input.status!=='draft')fail('准备器不能创建批准身份或更新已批准合同');
|
|
70
|
+
const result=structuredClone(input);Object.assign(result,{schema_version:2,kind:'api-contract-decision',status:'draft'});
|
|
71
|
+
const conflicts=[];
|
|
72
|
+
const bind=(value,label)=>{try{return {...value,...readContractSource(value,{root}).binding};}catch(error){conflicts.push({field:label,source_ref:value.ref,reason:error.message,conflicts:error.conflicts||[],recovery:'核对双方来源并补齐当前文件后重新准备'});return value;}};
|
|
73
|
+
result.assessment_digest=bind({ref:input.assessment_ref,...(input.assessment_digest?{digest:input.assessment_digest}:{})},'API 影响评估').digest;
|
|
74
|
+
for(const key of ['openapi','validation_record','draft_review'])if(result[key])result[key]=bind(result[key],key);
|
|
75
|
+
for(const ref of result.evidence_refs||[])try{readBound(root,ref,'API 决定证据');}catch(error){conflicts.push({field:'evidence_refs',source_ref:ref,reason:error.message,recovery:'补齐当前证据后重新准备'});}
|
|
76
|
+
if(conflicts.length)throw Object.assign(new TypeError(conflicts.map(item=>`${item.field}: ${item.reason}; 恢复: ${item.recovery}`).join('\n')),{code:'API_SOURCE_CONFLICT',conflicts});
|
|
77
|
+
validateJsonSchema(result,SCHEMA.replace('.schema','-v2.schema'),{label:'API Contract Decision v2 草案'});
|
|
78
|
+
return result;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function migrateApiContractDecision(binding,{root=TEMPLATE_ROOT,version}={}) {
|
|
82
|
+
return withValidationPhase({root,purpose:'api-migrate',slice_id:binding?.ref,readOnly:true},()=>{
|
|
83
|
+
const validated=validateApiContractDecision(binding,{root}),old=validated.decision;
|
|
84
|
+
if(old.schema_version!==1)fail('显式迁移只接受 v1');
|
|
85
|
+
if(!/^v[1-9][0-9]*$/.test(version)||BigInt(version.slice(1))<=BigInt(old.decision_version.slice(1)))fail('迁移必须选择新的递增决定版本');
|
|
86
|
+
const result=structuredClone(old);result.schema_version=2;result.decision_version=version;result.status='draft';delete result.current_version;
|
|
87
|
+
if(result.draft_review){result.evidence_refs=[...new Set([...result.evidence_refs,...result.draft_review.evidence_refs])];result.draft_review={ref:result.draft_review.ref,digest:result.draft_review.digest};result.freeze={version:result.freeze.version,frozen_at:result.freeze.frozen_at};}
|
|
88
|
+
return prepareApiContractDecision(result,{root});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assertApprovalUserDecision } from './user-decision-reuse.mjs';
|
|
2
|
-
import { existsSync, readFileSync } from
|
|
2
|
+
import { existsSync, readFileSync } from './validation-phase.mjs';
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
5
5
|
import {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import {validateJsonSchemas} from './json-schema.mjs';
|
|
2
|
+
import { assertScopeSlice, assertScopeWorkUnit } from './lifecycle-execution-scope.mjs';
|
|
1
3
|
import { selectSliceWorkUnit, normalizeSliceContract, sourceSliceContract, parseSliceYaml } from './slice-contract.mjs';
|
|
2
4
|
import path from 'node:path';
|
|
3
|
-
import { readFileSync } from '
|
|
5
|
+
import { readFileSync, inValidationPhase, validationMemo, validationPhaseToken } from './validation-phase.mjs';
|
|
4
6
|
import { ROOT, read, parse, safe, hash, digest, schema } from './strategic-handoff-io.mjs';
|
|
5
7
|
import { countersignRuleForGate } from './digital-human-roles.mjs';
|
|
6
8
|
import { validateApprovalRecord } from './approval-record.mjs';
|
|
7
9
|
import { assertImplementationDecision } from './user-decision.mjs';
|
|
8
10
|
import { loadSkillRegistry } from './skill-registry.mjs';
|
|
9
11
|
|
|
12
|
+
const primeCache=Symbol('approval-schema-prime');
|
|
10
13
|
const contexts=new WeakMap();
|
|
11
14
|
const gateId='gate.slice-contract-approved';
|
|
12
15
|
const check=(ok,code,message)=>{if(!ok){const error=new TypeError(`${code}: ${message}`);error.code=code;throw error;}};
|
|
@@ -68,27 +71,52 @@ function executionBasis(verified,{allowCompilerDrift=false}={}) {
|
|
|
68
71
|
return verified;
|
|
69
72
|
}
|
|
70
73
|
|
|
74
|
+
function primeApprovalSchemas(binding,root){
|
|
75
|
+
if(!inValidationPhase())return;
|
|
76
|
+
validationMemo(primeCache,JSON.stringify([root,binding]),()=>{
|
|
77
|
+
const jobs=[];
|
|
78
|
+
const add=(value,ref,formatChecker,errorStyle)=>jobs.push({value,schemaPath:path.join(ROOT,ref),formatChecker,errorStyle});
|
|
79
|
+
// Speculative shape checks never decide approval or change the original error order.
|
|
80
|
+
// Original consumers still validate every semantic and authorization rule below.
|
|
81
|
+
try{
|
|
82
|
+
const source=parseSliceYaml(readFileSync(safe(root,binding.ref))),raw=source.slice_contract||source;
|
|
83
|
+
if(raw.schema_version===3)add(raw,'docs/process/schemas/slice-implementation-contract-v3.schema.json',false,'verbose');
|
|
84
|
+
const approval=read(safe(root,binding.approval_ref));
|
|
85
|
+
if(approval.gates&&approval.human_review&&!approval.gate_id){
|
|
86
|
+
add(approval,'docs/process/schemas/lifecycle-checkpoint.schema.json',false,'verbose');
|
|
87
|
+
for(const requirement of approval.human_review.user_decisions||[])if(requirement.user_decision_ref&&!requirement.continuation_ref){const ref=path.isAbsolute(requirement.user_decision_ref)?path.relative(root,requirement.user_decision_ref):requirement.user_decision_ref;add(read(safe(root,ref)),'docs/process/schemas/user-decision.schema.json',true,'compact');}
|
|
88
|
+
}
|
|
89
|
+
}catch{/* The original consumer reports missing or invalid input with its existing code. */}
|
|
90
|
+
if(jobs.length)validateJsonSchemas(jobs);return true;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
71
93
|
export function createApprovedExecutionContext(binding,options={}) {
|
|
94
|
+
primeApprovalSchemas(binding,path.resolve(options.root||process.cwd()));
|
|
72
95
|
const verified=executionBasis(verifySliceContractApproval(binding,options));
|
|
73
|
-
selectSliceWorkUnit(verified.contract,options.work_unit_id);
|
|
96
|
+
assertScopeSlice(selectSliceWorkUnit(verified.contract,options.work_unit_id),{root:verified.root});
|
|
97
|
+
if(!options.readOnly)assertScopeWorkUnit('work-unit.slice-implementation',{root:verified.root});
|
|
74
98
|
const context=Object.freeze({kind:'approved-slice-execution-context'});
|
|
75
|
-
contexts.set(context,{binding:verified.binding,root:verified.root,work_unit_id:options.work_unit_id,allowCompilerDrift:false});
|
|
99
|
+
contexts.set(context,{phase:validationPhaseToken(),binding:verified.binding,root:verified.root,work_unit_id:options.work_unit_id,allowCompilerDrift:false,readOnly:options.readOnly===true});
|
|
76
100
|
return context;
|
|
77
101
|
}
|
|
78
102
|
|
|
79
103
|
/** A prior approved scope may authenticate bounded output while current compiler facts are recomputed. */
|
|
80
104
|
export function createApprovedRecompilationContext(binding,options={}) {
|
|
81
|
-
const verified=executionBasis(verifySliceContractApproval(binding,options),{allowCompilerDrift:true});
|
|
82
|
-
selectSliceWorkUnit(verified.contract,options.work_unit_id);
|
|
105
|
+
const verified=executionBasis(verifySliceContractApproval(binding,options),{allowCompilerDrift:true,readOnly:options.readOnly===true});
|
|
106
|
+
assertScopeSlice(selectSliceWorkUnit(verified.contract,options.work_unit_id),{root:verified.root});
|
|
107
|
+
if(!options.readOnly)assertScopeWorkUnit('work-unit.slice-implementation',{root:verified.root});
|
|
83
108
|
const context=Object.freeze({kind:'approved-slice-recompilation-context'});
|
|
84
|
-
contexts.set(context,{binding:verified.binding,root:verified.root,work_unit_id:options.work_unit_id,allowCompilerDrift:true});
|
|
109
|
+
contexts.set(context,{phase:validationPhaseToken(),binding:verified.binding,root:verified.root,work_unit_id:options.work_unit_id,allowCompilerDrift:true,readOnly:options.readOnly===true});
|
|
85
110
|
return context;
|
|
86
111
|
}
|
|
87
112
|
|
|
88
113
|
/** Revalidate original bytes on every boundary; a serialized/caller-invented context is rejected. */
|
|
89
|
-
export function assertApprovedExecutionContext(context,{root=process.cwd(),contract,architectureIdentity,architectureEvidence,technicalDesign,sliceId}={}) {
|
|
114
|
+
export function assertApprovedExecutionContext(context,{root=process.cwd(),contract,architectureIdentity,architectureEvidence,technicalDesign,sliceId,readOnly=false}={}) {
|
|
90
115
|
const saved=contexts.get(context);
|
|
91
116
|
check(saved&&saved.root===path.resolve(root),'EXECUTION_CONTEXT_UNTRUSTED','只允许从当前原始 Slice 与本地批准生成的执行上下文');
|
|
117
|
+
check(!saved.phase||saved.phase===validationPhaseToken(),'EXECUTION_CONTEXT_EXPIRED','不能跨验证阶段复用批准上下文');
|
|
118
|
+
check(!saved.readOnly||readOnly,'EXECUTION_CONTEXT_READ_ONLY','只读检查上下文不能执行实现');
|
|
119
|
+
if(!readOnly)assertScopeWorkUnit('work-unit.slice-implementation',{root});
|
|
92
120
|
const verified=executionBasis(verifySliceContractApproval(saved.binding,{root,contract}),{allowCompilerDrift:saved.allowCompilerDrift});
|
|
93
121
|
const current=selectSliceWorkUnit(verified.contract,saved.work_unit_id),resolution=current.resolution;
|
|
94
122
|
if(sliceId)check(current.slice_id===sliceId,'EXECUTION_SCOPE_CONFLICT','执行上下文属于另一切片');
|
|
@@ -37,9 +37,9 @@ export function validateArchitectureIdentity(identity, registry = loadSkillRegis
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// Existing projects must present original on-disk evidence, not three copies supplied by a caller.
|
|
40
|
-
export function verifyArchitectureEvidence(identity, evidence, { root, registry = loadSkillRegistry(), execution } = {}) {
|
|
40
|
+
export function verifyArchitectureEvidence(identity, evidence, { root, registry = loadSkillRegistry(), execution, readOnly=false } = {}) {
|
|
41
41
|
validateArchitectureIdentity(identity, registry);
|
|
42
|
-
if (identity.schema_version === 2) return verifyExistingArchitecture(identity, evidence, { root, registry, execution });
|
|
42
|
+
if (identity.schema_version === 2) return verifyExistingArchitecture(identity, evidence, { root, registry, execution, readOnly });
|
|
43
43
|
if (!evidence?.engineering_baseline || !evidence?.repository_registration || !evidence?.manifest) throw new TypeError("缺少工程基线、仓库登记或 Manifest 架构证据");
|
|
44
44
|
assertArchitectureAgreement(identity, evidence, registry);
|
|
45
45
|
return { source_kind: "scaffold", bindings: evidence };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { inspectBackendDelivery, openBackendDelivery } from './backend-delivery.mjs';
|
|
5
|
+
import { validateBackendReview } from './backend-review.mjs';
|
|
6
|
+
import { read, safe, hash, json, write, ensure, project } from './strategic-handoff-io.mjs';
|
|
7
|
+
import { loadExecutionScope, TERMINAL_REF } from './lifecycle-execution-scope.mjs';
|
|
8
|
+
|
|
9
|
+
async function inspect(root, record) {
|
|
10
|
+
project(root);
|
|
11
|
+
ensure(loadExecutionScope(root)?.scope_id === 'plan-to-backend', '需要 plan-to-backend 职责范围');
|
|
12
|
+
ensure(record.schema_version === 1 && record.kind === 'backend-delivery-terminal'
|
|
13
|
+
&& record.business_completed === false && record.release_authorized === false, '后端终点不是业务完成或发布批准');
|
|
14
|
+
for (const name of ['delivery', 'review_state']) {
|
|
15
|
+
ensure(hash(readFileSync(safe(root, record[name]?.ref))) === record[name].digest, `${name} 原始字节变化`);
|
|
16
|
+
}
|
|
17
|
+
const state = read(safe(root, record.review_state.ref));
|
|
18
|
+
const { delivery } = await inspectBackendDelivery(root, record.delivery.ref);
|
|
19
|
+
ensure(state.review_input?.scope_kind === 'change' && state.review_input.slice_contract_ref === delivery.slice_contract.ref, '独立审查未绑定交付的 Slice');
|
|
20
|
+
ensure(validateBackendReview(state, { root }).status === 'passed', '后端交付需要当前独立审查');
|
|
21
|
+
const commit = execFileSync('git', ['-C', path.resolve(root, state.review_input.project_root), 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
|
|
22
|
+
ensure(state.review_input.review_mode === 'committed' && delivery.build.source_commit === commit, '终点需要与当前已提交审查候选一致的真实构建源码提交');
|
|
23
|
+
ensure(record.downstream && ['owner', 'ticket_ref', 'verification_plan', 'target_version'].every(key => typeof record.downstream[key] === 'string' && record.downstream[key].trim()), '终点缺少下游接收责任和待办');
|
|
24
|
+
return openBackendDelivery(safe(root, record.bundle_ref), bundle => {
|
|
25
|
+
ensure(record.bundle_digest === bundle.manifest.bundle_digest && bundle.manifest.delivery_ref === record.delivery.ref, '终点绑定的交付包不一致');
|
|
26
|
+
// A different valid package must not be used to close the current source.
|
|
27
|
+
for (const file of bundle.manifest.files) if (file.original_ref) {
|
|
28
|
+
ensure(hash(readFileSync(safe(root, file.original_ref))) === file.sha256, `包与当前源不同: ${file.original_ref}`);
|
|
29
|
+
}
|
|
30
|
+
return { result: 'backend-delivered', delivery_id: delivery.delivery_id, version: delivery.version,
|
|
31
|
+
bundle_digest: bundle.manifest.bundle_digest, next_work_unit: null, business_completed: false,
|
|
32
|
+
release_authorized: false, live_service_checked: false, downstream: record.downstream };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function verifyBackendDeliveryTerminal(root) {
|
|
37
|
+
return inspect(root, read(safe(root, TERMINAL_REF)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** This closes only the backend responsibility after real package and review validation. */
|
|
41
|
+
export async function completeBackendDelivery(root, input) {
|
|
42
|
+
root = path.resolve(root);
|
|
43
|
+
const record = { ...input, schema_version: 1, kind: 'backend-delivery-terminal', business_completed: false, release_authorized: false };
|
|
44
|
+
ensure(!existsSync(path.join(root, TERMINAL_REF)), '终点已存在;只能复验,不覆盖旧终点');
|
|
45
|
+
const result = await inspect(root, record);
|
|
46
|
+
const bytes = json(record);
|
|
47
|
+
write(root, TERMINAL_REF, bytes);
|
|
48
|
+
try { await verifyBackendDeliveryTerminal(root); }
|
|
49
|
+
catch (error) { if (readFileSync(safe(root, TERMINAL_REF), 'utf8') === bytes) rmSync(safe(root, TERMINAL_REF)); throw error; }
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Version qualification binds executable sources and generated bytes, not Skill prose. */
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { readFileSync, readdirSync, lstatSync } from '
|
|
3
|
+
import { readFileSync, readdirSync, lstatSync } from './validation-phase.mjs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assertSourceFingerprint } from "./backend-platform-provenance.mjs";
|
|
2
2
|
/** Shared, exact backend platform selection. Catalog entries are maintenance-owned. */
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import { readFileSync } from
|
|
4
|
+
import { readFileSync } from './validation-phase.mjs';
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, readFileSync } from
|
|
2
|
+
import { existsSync, readFileSync } from './validation-phase.mjs';
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
5
5
|
import { loadApprovalRecord, validateApprovalRecord } from "./approval-record.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, lstatSync, readFileSync, readdirSync } from
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from './validation-phase.mjs';
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
const CONTEXT_FILE = "CONTEXT.md";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from
|
|
1
|
+
import { existsSync, readFileSync } from './validation-phase.mjs';
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
4
4
|
import { resolveContextTermRefs, verifyContextSnapshot } from "./context-contract.mjs";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {readFileSync} from './validation-phase.mjs';
|
|
2
|
+
import {safe,hash} from './strategic-handoff-io.mjs';
|
|
3
|
+
import {parseSliceYaml} from './slice-contract.mjs';
|
|
4
|
+
export function contractSourceMetadata(bytes){
|
|
5
|
+
const text=String(bytes),front=/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
|
|
6
|
+
if(front)return parseSliceYaml(front[1]);
|
|
7
|
+
return /^\s*[{[]/.test(text)||/^[A-Za-z_][\w-]*:\s/m.test(text)&&!/^#/m.test(text)?parseSliceYaml(text):{};
|
|
8
|
+
}
|
|
9
|
+
/** Reads original bytes; returns derivable facts only. It never selects or approves a source. */
|
|
10
|
+
export function readContractSource(selection,{root=process.cwd(),read=ref=>readFileSync(safe(root,ref))}={}){
|
|
11
|
+
const selected=typeof selection==='string'?{ref:selection}:selection;
|
|
12
|
+
if(!selected?.ref)throw new TypeError('来源选择缺少 ref');
|
|
13
|
+
const bytes=read(selected.ref),digest=hash(bytes),data=contractSourceMetadata(bytes);
|
|
14
|
+
const observed=data.requirement_version??data.version??data.contract_version??data.decision_version;
|
|
15
|
+
const conflicts=[];
|
|
16
|
+
const conflict=(field,before,after)=>conflicts.push({field,source_ref:selected.ref,before,after});
|
|
17
|
+
if(selected.digest&&selected.digest!==digest)conflict('digest',selected.digest,digest);
|
|
18
|
+
if(selected.version!==undefined&&observed!==undefined&&String(selected.version)!==String(observed))conflict('version',selected.version,observed);
|
|
19
|
+
if(conflicts.length){const error=new TypeError(conflicts.map(({field,before,after})=>`${field==='version'?'来源版本冲突':'来源冲突'} ${selected.ref}.${field}: supplied=${before}; observed=${after}; 恢复: 核对双方来源并显式选择当前版本`).join('\n'));Object.assign(error,{code:'SLICE_SOURCE_CONFLICT',...conflicts[0],conflicts});throw error;}
|
|
20
|
+
const version=selected.version??observed;
|
|
21
|
+
return {bytes,data,binding:{ref:selected.ref,digest,...(version!==undefined?{version:String(version)}:{}),...(selected.approval_ref?{approval_ref:selected.approval_ref}:{})}};
|
|
22
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {readFileSync,withValidationPhase} from './validation-phase.mjs';
|
|
2
|
+
import {safe,hash,schema} from './strategic-handoff-io.mjs';
|
|
3
|
+
import {parseSliceYaml,readSliceContract} from './slice-contract.mjs';
|
|
4
|
+
import {renderSliceContractView,diffSliceContracts} from './slice-contract-views.mjs';
|
|
5
|
+
|
|
6
|
+
// This is a presentation adapter table, never a lifecycle or approval registry.
|
|
7
|
+
export const contractKinds=Object.freeze({
|
|
8
|
+
slice:null,plan:null,spec:null,'product-design':null,'prototype-confirmation':null,'visual-baseline':null,
|
|
9
|
+
'technical-design':'.agents/skills/yss-technical-design/references/technical-design.schema.json',
|
|
10
|
+
'api-decision':'docs/process/schemas/api-contract-decision.schema.json',
|
|
11
|
+
'data-decision':'docs/process/schemas/data-architecture-decision.schema.json',
|
|
12
|
+
scaffold:'docs/process/schemas/project-scaffold-contract.schema.json',
|
|
13
|
+
'scaffold-decision':'docs/process/schemas/scaffold-architecture-decisions.schema.json',
|
|
14
|
+
handoff:null,'backend-delivery':'docs/process/schemas/backend-delivery.schema.json',
|
|
15
|
+
'frontend-acceptance':'docs/process/schemas/frontend-delivery-acceptance.schema.json',approval:null,'user-decision':'docs/process/schemas/user-decision.schema.json',
|
|
16
|
+
'context-reconciliation':'docs/process/schemas/context-reconciliation.schema.json'
|
|
17
|
+
});
|
|
18
|
+
const object=v=>v&&typeof v==='object'&&!Array.isArray(v);
|
|
19
|
+
const stringify=v=>typeof v==='string'?v:JSON.stringify(v,null,2);
|
|
20
|
+
const metadata=/^(schema_version|kind|digest|sha256|subject_digest|.*_digest|.*_sha256|resolution|basis|inputs|sources|artifact_bindings|current_version)$/;
|
|
21
|
+
function display(value){
|
|
22
|
+
if(Array.isArray(value))return value.map(display);
|
|
23
|
+
if(!object(value))return value;
|
|
24
|
+
return Object.fromEntries(Object.entries(value).filter(([key])=>!metadata.test(key)).map(([k,v])=>[k,display(v)]));
|
|
25
|
+
}
|
|
26
|
+
function validateKind(kind){if(!Object.hasOwn(contractKinds,kind))throw new TypeError(`未知合同类型 ${kind};可用:${Object.keys(contractKinds).join(', ')}`);}
|
|
27
|
+
function load(ref,root){
|
|
28
|
+
const bytes=readFileSync(safe(root,ref));
|
|
29
|
+
if(/\.md$/i.test(ref)){
|
|
30
|
+
const text=bytes.toString('utf8'),front=/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
|
|
31
|
+
return{bytes,raw:{...(front?parseSliceYaml(front[1]):{}),正文:front?text.slice(front[0].length):text}};
|
|
32
|
+
}
|
|
33
|
+
const raw=parseSliceYaml(bytes);if(!object(raw))throw new TypeError('合同必须是结构化对象或 Markdown 文档');return{bytes,raw};
|
|
34
|
+
}
|
|
35
|
+
function references(value,root,rows=[],seen=new Set()){
|
|
36
|
+
if(!value||typeof value!=='object')return rows;
|
|
37
|
+
if(object(value)&&typeof value.ref==='string'&&typeof value.digest==='string'){
|
|
38
|
+
const key=`${value.ref}\0${value.digest}`;
|
|
39
|
+
if(!seen.has(key)){seen.add(key);try{rows.push({ref:value.ref,current:hash(readFileSync(safe(root,value.ref)))===value.digest});}catch(error){rows.push({ref:value.ref,current:false,error:error.message});}}
|
|
40
|
+
}
|
|
41
|
+
if(object(value))for(const [key,ref]of Object.entries(value))if(key.endsWith('_ref')&&typeof ref==='string'&&typeof value[key.replace(/_ref$/,'_digest')]==='string')references({ref,digest:value[key.replace(/_ref$/,'_digest')]},root,rows,seen);
|
|
42
|
+
for(const child of Object.values(value))references(child,root,rows,seen);
|
|
43
|
+
return rows;
|
|
44
|
+
}
|
|
45
|
+
function declaredBlockers(value,prefix=''){
|
|
46
|
+
if(!value||typeof value!=='object')return[];
|
|
47
|
+
const rows=[];
|
|
48
|
+
for(const [key,child]of Object.entries(value)){
|
|
49
|
+
const field=prefix?`${prefix}.${key}`:key;
|
|
50
|
+
if(['blockers','blocking_findings','stale_inputs','readiness_blockers'].includes(key)&&Array.isArray(child))for(const item of child)rows.push(`${field}: ${stringify(item)}`);
|
|
51
|
+
else rows.push(...declaredBlockers(child,field));
|
|
52
|
+
}
|
|
53
|
+
return rows;
|
|
54
|
+
}
|
|
55
|
+
function markdown(view){
|
|
56
|
+
const lines=[`# ${view.kind} · ${view.binding.id||view.binding.ref}`,`版本:${view.binding.version||'源资产未声明'};权威文件:${view.binding.ref}`,`摘要:${view.binding.digest}`,`检查:${view.checks.join(';')}。批准有效性未核验;本视图不授予执行权限。`];
|
|
57
|
+
for(const [name,value]of Object.entries(view.content))if(value!==undefined)lines.push(`## ${name}`,stringify(value));
|
|
58
|
+
if(view.blockers.length)lines.push('## 阻断与未决检查',...view.blockers.map(x=>`- ${x}`));
|
|
59
|
+
return lines.join('\n\n')+'\n';
|
|
60
|
+
}
|
|
61
|
+
export function viewContract(ref,options={}){return withValidationPhase({root:options.root,purpose:'contract-view',slice_id:ref,work_unit_id:options.unit_id,readOnly:true},()=>view(ref,options));}
|
|
62
|
+
function view(ref,{root=process.cwd(),kind,profile='review',unit_id}={}){
|
|
63
|
+
validateKind(kind);if(!['review','task','full'].includes(profile))throw new TypeError(`未知阅读档位 ${profile}`);
|
|
64
|
+
let binding,content,checks=['可读取'],blockers=[],sourceChecks=[];
|
|
65
|
+
if(kind==='slice'){
|
|
66
|
+
const loaded=readSliceContract(ref,{root,diagnostic:true});binding=loaded.binding;blockers.push(...declaredBlockers(loaded.raw));
|
|
67
|
+
const legacy=renderSliceContractView(ref,{root,unit_id:profile==='task'?unit_id:undefined});
|
|
68
|
+
checks.push('Slice 结构及绑定来源');
|
|
69
|
+
if(legacy.diagnostic_only){content={诊断:legacy.blockers};blockers=legacy.blockers.map(b=>b.reason);}
|
|
70
|
+
else if(profile==='full')content={权威合同:loaded.raw,完整视图:legacy};
|
|
71
|
+
else if(profile==='review'){
|
|
72
|
+
content={...legacy.summary,验收:legacy.acceptance,实施范围:{工程:loaded.raw.scope?.project_roots||legacy.constraints.project_roots,允许写入:loaded.raw.scope?.allowed_write_paths||legacy.constraints.allowed_write_paths},关键约束:loaded.raw.scope?.forbidden_patterns||legacy.constraints.forbidden_patterns,停止条件:'来源或批准过期、越界、缺证据、验证失败、drift / violation / new_impacts 时停止并回交。'};
|
|
73
|
+
for(const key of ['risks','risk_acceptance','blockers','pending_decisions','constraints'])if(loaded.raw[key])content[key]=loaded.raw[key];
|
|
74
|
+
for(const [label,pattern]of [['风险',/风险/],['关键取舍',/取舍/],['待决定事项',/待|决定/],['变化',/变化|变更/]])if(!Object.keys(content).some(k=>pattern.test(k)))content[label]='来源未说明,审阅时核实。';
|
|
75
|
+
const extensions=loaded.raw.extensions||{};
|
|
76
|
+
for(const [name,extension]of Object.entries(extensions))for(const [key,value]of Object.entries(extension))if(/risk|constraint|decision|block|exception|unknown/.test(key))content[`${name}.${key}`]=value;
|
|
77
|
+
}else{
|
|
78
|
+
if(!unit_id)throw new TypeError('task 视图必须指定 --unit');
|
|
79
|
+
const units=loaded.contract.work_units.filter(u=>u.id===unit_id);if(units.length!==1)throw new TypeError(`工作单元缺失或不唯一: ${unit_id}`);
|
|
80
|
+
content={当前任务:units[0].work_unit||units[0],全局约束:legacy.constraints,专项约束:legacy.extensions,编译约束:loaded.raw.resolution,适用性:loaded.raw.applicability,验收:legacy.acceptance,上游引用:Object.fromEntries(Object.entries(loaded.sources).map(([k,v])=>[k,{ref:v.ref,version:v.version}])),停止条件:'来源或批准过期、越界、缺证据、验证失败、drift / violation / new_impacts 时停止并回交。'};
|
|
81
|
+
const known=new Set(['schema_version','contract_id','contract_version','slice_id','status','basis','scope','applicability','resolution','acceptance','verification','work_units','extensions','ticket_policy']);
|
|
82
|
+
content.其他约束=Object.fromEntries(Object.entries(loaded.raw).filter(([k])=>!known.has(k)));
|
|
83
|
+
}
|
|
84
|
+
}else{
|
|
85
|
+
const {bytes,raw}=load(ref,root);binding={ref,id:raw.contract_id||raw.technical_design_id||raw.decision_id||raw.request?.request_id||raw.handoff_id||raw.baseline_id||raw.spec_id||raw.plan_id||raw.id||null,version:raw.contract_version||raw.decision_version||raw.handoff_version||raw.version||null,digest:hash(bytes)};
|
|
86
|
+
blockers.push(...declaredBlockers(raw));
|
|
87
|
+
let schemaRef=contractKinds[kind];if(kind==='api-decision'&&raw.schema_version===2)schemaRef='docs/process/schemas/api-contract-decision-v2.schema.json';
|
|
88
|
+
if(kind==='frontend-acceptance'&&[1,3].includes(raw.schema_version))schemaRef=`docs/process/schemas/frontend-delivery-acceptance${raw.schema_version===1?'-v1':'-v3'}.schema.json`;
|
|
89
|
+
if(schemaRef)try{schema(raw,schemaRef);checks.push('结构校验');}catch(error){blockers.push(error.message);}
|
|
90
|
+
else checks.push('结构与批准未核验,按资产所有者规则继续');
|
|
91
|
+
sourceChecks=references(raw,root);if(sourceChecks.length)checks.push(`核对 ${sourceChecks.length} 项可识别来源摘要;其余语义由资产所有者核验`);for(const source of sourceChecks)if(!source.current)blockers.push(`来源不可读或过期:${source.ref}`);
|
|
92
|
+
// Unknown business fields remain visible. Only well-known machine metadata is hidden.
|
|
93
|
+
content=profile==='full'?{权威合同:raw,来源核对:sourceChecks}:display(raw);
|
|
94
|
+
if(kind==='api-decision'&&raw.schema_version===2&&raw.draft_review){try{const review=load(raw.draft_review.ref,root).raw;content.实际审查记录=profile==='full'?review:display(review);if(review.result!=='approved'||!Array.isArray(review.blocking_findings)||review.blocking_findings.length)blockers.push('OpenAPI Draft Review 实际记录未批准或有阻断');if(review.draft_ref!==raw.openapi?.ref||review.draft_digest!==raw.openapi?.digest)blockers.push('Draft Review 未绑定当前 OpenAPI');}catch(error){blockers.push(error.message);}}
|
|
95
|
+
if(profile==='task'){
|
|
96
|
+
if(raw.work_units){if(!unit_id)throw new TypeError('含工作单元的合同必须指定 --unit');const matches=raw.work_units.filter(x=>x.id===unit_id);if(matches.length!==1)throw new TypeError(`工作单元缺失或不唯一: ${unit_id}`);content.work_units=matches;}
|
|
97
|
+
content.机器绑定中的约束=Object.fromEntries(['resolution','basis','inputs','sources','artifact_bindings'].filter(k=>raw[k]!==undefined).map(k=>[k,raw[k]]));
|
|
98
|
+
content.约束分类='保留未分类字段;本视图只允许阅读,执行影响由资产所有者核验。';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const view={read_only:true,execution_allowed:false,kind,profile,binding,checks,approval_validity:'not-checked',content,blockers};view.markdown=markdown(view);return view;
|
|
102
|
+
}
|
|
103
|
+
export function diffContract(beforeRef,afterRef,{root=process.cwd(),beforeRoot=root,kind}={}){
|
|
104
|
+
validateKind(kind);if(kind==='slice')return diffSliceContracts(beforeRef,afterRef,{root,beforeRoot});
|
|
105
|
+
const before=load(beforeRef,beforeRoot),after=load(afterRef,root),changes=[];
|
|
106
|
+
const walk=(a,b,p)=>{if(JSON.stringify(a)===JSON.stringify(b))return;if(object(a)&&object(b)){for(const key of new Set([...Object.keys(a),...Object.keys(b)]))walk(a[key],b[key],p?`${p}.${key}`:key);}else changes.push({path:p,before:a??null,after:b??null,category:metadata.test(p.split('.').at(-1))?'binding-change':'requires-review'});};
|
|
107
|
+
walk(before.raw,after.raw,'');const source_checks=[...references(before.raw,beforeRoot).map(x=>({...x,side:'before'})),...references(after.raw,root).map(x=>({...x,side:'after'}))];
|
|
108
|
+
return{read_only:true,approval_reusable:false,execution_allowed:false,before:{ref:beforeRef,digest:hash(before.bytes)},after:{ref:afterRef,digest:hash(after.bytes)},bytes_changed:!before.bytes.equals(after.bytes),changes,source_checks,decision_impact:'必须按当前差异和授权延续协议核验;无法读取旧来源时影响未知。'};
|
|
109
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { normalizeSliceContract, sourceSliceContract, parseSliceYaml } from './slice-contract.mjs';
|
|
2
2
|
import { assertExistingSliceStructure, createApprovedExecutionContext, verifySliceContractApproval } from './approved-execution-context.mjs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { readFileSync, existsSync } from '
|
|
4
|
+
import { readFileSync, existsSync, withValidationPhase } from './validation-phase.mjs';
|
|
5
5
|
import { ROOT, read, parse, safe, hash, digest, ensure, schema, sourceApprovalPolicy } from './strategic-handoff-io.mjs';
|
|
6
6
|
import { parseContextContract } from './context-contract.mjs';
|
|
7
7
|
import { validateApprovalRecord } from './approval-record.mjs';
|
|
@@ -47,7 +47,8 @@ function requireBackendCapability(key) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/** Read-only: never executes input commands, starts services, unpacks archives or writes receipts. */
|
|
50
|
-
export async function preflightDelivery(input,{
|
|
50
|
+
export async function preflightDelivery(input,options={}) {return withValidationPhase({root:path.resolve(options.root||process.cwd(),typeof input?.governance_root==='string'?input.governance_root:'.'),purpose:`delivery-${options.stage}`,slice_id:input?.scope?.slice_id,work_unit_id:options.work_unit_id,readOnly:true,signal:options.signal},()=>preflight(input,options));}
|
|
51
|
+
async function preflight(input,{stage,root=process.cwd(),work_unit_id}={}) {
|
|
51
52
|
try { ensure(STAGES.includes(stage),'未知阶段;必须是 prepare|build|export|accept');schema(input,'docs/process/schemas/delivery-preflight-input.schema.json'); }
|
|
52
53
|
catch(error) { return invalidPreflightInput(error.message); }
|
|
53
54
|
root=path.resolve(root,input.governance_root||'.');
|