create-yss-spec 3.4.1 → 3.4.4
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 +1 -1
- package/package.json +2 -2
- package/template/.agents/skills/.strategic-design-skills-manifest.json +3 -3
- package/template/.agents/skills/archify/SKILL.md +1 -1
- package/template/.agents/skills/code-review/SKILL.md +12 -8
- package/template/.agents/skills/file-export-download/SKILL.md +1 -1
- package/template/.agents/skills/frontend-commit/SKILL.md +1 -1
- package/template/.agents/skills/java-backend-commit/SKILL.md +1 -1
- package/template/.agents/skills/llm-wiki/SKILL.md +1 -6
- package/template/.agents/skills/lombok/SKILL.md +10 -336
- package/template/.agents/skills/lombok/references/annotation-examples.md +307 -0
- package/template/.agents/skills/maintaining-skills/SKILL.md +2 -1
- package/template/.agents/skills/maintaining-skills/references/authoring.md +27 -0
- package/template/.agents/skills/mapstruct/SKILL.md +11 -318
- package/template/.agents/skills/mapstruct/references/annotation-examples.md +300 -0
- package/template/.agents/skills/page-form-module/SKILL.md +1 -1
- package/template/.agents/skills/page-skeleton/SKILL.md +1 -1
- package/template/.agents/skills/theme-token-usage/SKILL.md +1 -1
- package/template/.agents/skills/to-tickets/SKILL.md +1 -1
- package/template/.agents/skills/using-git-worktrees/SKILL.md +1 -1
- package/template/.agents/skills/wayfinder/SKILL.md +1 -1
- package/template/.agents/skills/yedit-table-usage/SKILL.md +1 -1
- package/template/.agents/skills/yss-api-integration/SKILL.md +1 -1
- package/template/.agents/skills/yss-audit-log/SKILL.md +1 -1
- package/template/.agents/skills/yss-cache/SKILL.md +1 -1
- package/template/.agents/skills/yss-ddd-scaffold-generator/SKILL.md +16 -3
- package/template/.agents/skills/yss-ddd-scaffold-generator/assets/templates/pom/application-pom.xml.template +1 -1
- package/template/.agents/skills/yss-ddd-scaffold-generator/assets/templates/pom/parent-pom.xml.template +15 -10
- package/template/.agents/skills/yss-ddd-scaffold-generator/assets/templates/pom/web-pom.xml.template +3 -3
- package/template/.agents/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +3 -3
- package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +5 -1
- package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +5 -2
- package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +13 -10
- package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +2 -1
- package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +35 -3
- package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +1 -1
- package/template/.agents/skills/yss-distributed-id/SKILL.md +1 -1
- package/template/.agents/skills/yss-dto/SKILL.md +5 -1
- package/template/.agents/skills/yss-excel-mvc/SKILL.md +1 -1
- package/template/.agents/skills/yss-exception/SKILL.md +5 -1
- package/template/.agents/skills/yss-formily-schema-generator/SKILL.md +1 -1
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +14 -6
- package/template/.agents/skills/yss-implementation-contract-compiler/agents/openai.yaml +1 -1
- package/template/.agents/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/references/slice-implementation-contract-v2.md +214 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +66 -204
- package/template/.agents/skills/yss-layered-mvc-scaffold-generator/SKILL.md +15 -2
- package/template/.agents/skills/yss-layered-mvc-scaffold-generator/scripts/generate_scaffold.mjs +28 -19
- package/template/.agents/skills/yss-layered-mvc-scaffold-generator/scripts/scaffold-generator.test.mjs +10 -10
- package/template/.agents/skills/yss-mybatis/SKILL.md +4 -0
- package/template/.agents/skills/yss-openapi-draft-review/SKILL.md +1 -1
- package/template/.agents/skills/yss-openapi-governance/SKILL.md +1 -1
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +4 -4
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +29 -7
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +6 -2
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +3 -1
- package/template/.agents/skills/yss-product-lifecycle/references/user-decisions.md +24 -4
- package/template/.agents/skills/yss-prototype-stage/SKILL.md +1 -1
- package/template/.agents/skills/yss-repository/SKILL.md +4 -0
- package/template/.agents/skills/yss-research/SKILL.md +1 -1
- package/template/.agents/skills/yss-resilience4j/SKILL.md +1 -1
- package/template/.agents/skills/yss-security-algorithm/SKILL.md +1 -1
- package/template/.agents/skills/yss-stage-decision/SKILL.md +1 -1
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy-contract.md +2 -0
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
- package/template/.agents/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +3 -3
- package/template/.agents/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +3 -3
- package/template/.agents/skills/yss-stage-decision/tests/run-scenarios.mjs +33 -0
- package/template/.agents/skills/yss-tactical-design/SKILL.md +5 -1
- package/template/.agents/skills/yss-technical-design/SKILL.md +5 -1
- package/template/.agents/skills/yss-technical-design/references/technical-design-contract.md +1 -1
- package/template/.agents/skills/yss-technical-design/scripts/validate-technical-design.mjs +2 -2
- package/template/.agents/skills/yss-technical-design/tests/run-scenarios.mjs +1 -1
- package/template/.agents/skills/yss-ui/SKILL.md +1 -1
- package/template/.agents/skills/yss-ui-business-page-generation/SKILL.md +1 -1
- package/template/.agents/skills/yss-up-springboot3/SKILL.md +1 -1
- package/template/.agents/skills/yss-use-table-height/SKILL.md +1 -1
- package/template/.agents/skills/yss-use-tree-height/SKILL.md +1 -1
- package/template/.agents/skills/yss-userinfo/SKILL.md +1 -1
- package/template/.agents/skills/yss-validation/SKILL.md +5 -1
- package/template/.agents/skills/yss-web-controller/SKILL.md +5 -1
- package/template/.agents/skills/yss-web-controller/scripts/generate_controller.mjs +5 -2
- package/template/.agents/skills/ytable-usage/SKILL.md +1 -1
- package/template/.agents/skills/ytree-usage/SKILL.md +1 -1
- package/template/.codex/skills/archify/SKILL.md +1 -1
- package/template/.codex/skills/code-review/SKILL.md +12 -8
- package/template/.codex/skills/data-analytics/skills/analyze-data-quality/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/build-dashboard/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/build-report/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/design-kpis/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/gather-business-context/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/index/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/jupyter-notebooks/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/kpi-reporting/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/market-sizing/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/metric-diagnostics/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/product-business-analysis/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/spreadsheets/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/validate-data/SKILL.md +1 -1
- package/template/.codex/skills/data-analytics/skills/visualize-data/SKILL.md +1 -1
- package/template/.codex/skills/file-export-download/SKILL.md +1 -1
- package/template/.codex/skills/frontend-commit/SKILL.md +1 -1
- package/template/.codex/skills/java-backend-commit/SKILL.md +1 -1
- package/template/.codex/skills/llm-wiki/SKILL.md +1 -6
- package/template/.codex/skills/lombok/SKILL.md +10 -336
- package/template/.codex/skills/lombok/references/annotation-examples.md +307 -0
- package/template/.codex/skills/maintaining-skills/SKILL.md +2 -1
- package/template/.codex/skills/maintaining-skills/references/authoring.md +27 -0
- package/template/.codex/skills/mapstruct/SKILL.md +11 -318
- package/template/.codex/skills/mapstruct/references/annotation-examples.md +300 -0
- package/template/.codex/skills/page-form-module/SKILL.md +1 -1
- package/template/.codex/skills/page-skeleton/SKILL.md +1 -1
- package/template/.codex/skills/product-design/skills/audit/SKILL.md +1 -1
- package/template/.codex/skills/product-design/skills/design-qa/SKILL.md +1 -1
- package/template/.codex/skills/product-design/skills/get-context/SKILL.md +1 -1
- package/template/.codex/skills/product-design/skills/ideate/SKILL.md +1 -1
- package/template/.codex/skills/product-design/skills/image-to-code/SKILL.md +1 -1
- package/template/.codex/skills/product-design/skills/index/SKILL.md +1 -1
- package/template/.codex/skills/product-design/skills/prototype/SKILL.md +1 -1
- package/template/.codex/skills/product-design/skills/research/SKILL.md +1 -1
- package/template/.codex/skills/product-design/skills/user-context/SKILL.md +1 -1
- package/template/.codex/skills/theme-token-usage/SKILL.md +1 -1
- package/template/.codex/skills/to-tickets/SKILL.md +1 -1
- package/template/.codex/skills/using-git-worktrees/SKILL.md +1 -1
- package/template/.codex/skills/wayfinder/SKILL.md +1 -1
- package/template/.codex/skills/yedit-table-usage/SKILL.md +1 -1
- package/template/.codex/skills/yss-api-integration/SKILL.md +1 -1
- package/template/.codex/skills/yss-audit-log/SKILL.md +1 -1
- package/template/.codex/skills/yss-cache/SKILL.md +1 -1
- package/template/.codex/skills/yss-ddd-scaffold-generator/SKILL.md +16 -3
- package/template/.codex/skills/yss-ddd-scaffold-generator/assets/templates/pom/application-pom.xml.template +1 -1
- package/template/.codex/skills/yss-ddd-scaffold-generator/assets/templates/pom/parent-pom.xml.template +15 -10
- package/template/.codex/skills/yss-ddd-scaffold-generator/assets/templates/pom/web-pom.xml.template +3 -3
- package/template/.codex/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +3 -3
- package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +5 -1
- package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +5 -2
- package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +13 -10
- package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +2 -1
- package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +35 -3
- package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +1 -1
- package/template/.codex/skills/yss-distributed-id/SKILL.md +1 -1
- package/template/.codex/skills/yss-dto/SKILL.md +5 -1
- package/template/.codex/skills/yss-excel-mvc/SKILL.md +1 -1
- package/template/.codex/skills/yss-exception/SKILL.md +5 -1
- package/template/.codex/skills/yss-formily-schema-generator/SKILL.md +1 -1
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +14 -6
- package/template/.codex/skills/yss-implementation-contract-compiler/agents/openai.yaml +1 -1
- package/template/.codex/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/references/slice-implementation-contract-v2.md +214 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +66 -204
- package/template/.codex/skills/yss-layered-mvc-scaffold-generator/SKILL.md +15 -2
- package/template/.codex/skills/yss-layered-mvc-scaffold-generator/scripts/generate_scaffold.mjs +28 -19
- package/template/.codex/skills/yss-layered-mvc-scaffold-generator/scripts/scaffold-generator.test.mjs +10 -10
- package/template/.codex/skills/yss-mybatis/SKILL.md +4 -0
- package/template/.codex/skills/yss-openapi-draft-review/SKILL.md +1 -1
- package/template/.codex/skills/yss-openapi-governance/SKILL.md +1 -1
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +4 -4
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +29 -7
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +6 -2
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +3 -1
- package/template/.codex/skills/yss-product-lifecycle/references/user-decisions.md +24 -4
- package/template/.codex/skills/yss-prototype-stage/SKILL.md +1 -1
- package/template/.codex/skills/yss-repository/SKILL.md +4 -0
- package/template/.codex/skills/yss-research/SKILL.md +1 -1
- package/template/.codex/skills/yss-resilience4j/SKILL.md +1 -1
- package/template/.codex/skills/yss-security-algorithm/SKILL.md +1 -1
- package/template/.codex/skills/yss-stage-decision/SKILL.md +1 -1
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy-contract.md +2 -0
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
- package/template/.codex/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +3 -3
- package/template/.codex/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +3 -3
- package/template/.codex/skills/yss-stage-decision/tests/run-scenarios.mjs +33 -0
- package/template/.codex/skills/yss-tactical-design/SKILL.md +5 -1
- package/template/.codex/skills/yss-technical-design/SKILL.md +5 -1
- package/template/.codex/skills/yss-technical-design/references/technical-design-contract.md +1 -1
- package/template/.codex/skills/yss-technical-design/scripts/validate-technical-design.mjs +2 -2
- package/template/.codex/skills/yss-technical-design/tests/run-scenarios.mjs +1 -1
- package/template/.codex/skills/yss-ui/SKILL.md +1 -1
- package/template/.codex/skills/yss-ui-business-page-generation/SKILL.md +1 -1
- package/template/.codex/skills/yss-up-springboot3/SKILL.md +1 -1
- package/template/.codex/skills/yss-use-table-height/SKILL.md +1 -1
- package/template/.codex/skills/yss-use-tree-height/SKILL.md +1 -1
- package/template/.codex/skills/yss-userinfo/SKILL.md +1 -1
- package/template/.codex/skills/yss-validation/SKILL.md +5 -1
- package/template/.codex/skills/yss-web-controller/SKILL.md +5 -1
- package/template/.codex/skills/yss-web-controller/scripts/generate_controller.mjs +5 -2
- package/template/.codex/skills/ytable-usage/SKILL.md +1 -1
- package/template/.codex/skills/ytree-usage/SKILL.md +1 -1
- package/template/.cursor/skills/archify/SKILL.md +1 -1
- package/template/.cursor/skills/code-review/SKILL.md +12 -8
- package/template/.cursor/skills/file-export-download/SKILL.md +1 -1
- package/template/.cursor/skills/frontend-commit/SKILL.md +1 -1
- package/template/.cursor/skills/java-backend-commit/SKILL.md +1 -1
- package/template/.cursor/skills/llm-wiki/SKILL.md +1 -6
- package/template/.cursor/skills/lombok/SKILL.md +10 -336
- package/template/.cursor/skills/lombok/references/annotation-examples.md +307 -0
- package/template/.cursor/skills/maintaining-skills/SKILL.md +2 -1
- package/template/.cursor/skills/maintaining-skills/references/authoring.md +27 -0
- package/template/.cursor/skills/mapstruct/SKILL.md +11 -318
- package/template/.cursor/skills/mapstruct/references/annotation-examples.md +300 -0
- package/template/.cursor/skills/page-form-module/SKILL.md +1 -1
- package/template/.cursor/skills/page-skeleton/SKILL.md +1 -1
- package/template/.cursor/skills/theme-token-usage/SKILL.md +1 -1
- package/template/.cursor/skills/to-tickets/SKILL.md +1 -1
- package/template/.cursor/skills/using-git-worktrees/SKILL.md +1 -1
- package/template/.cursor/skills/wayfinder/SKILL.md +1 -1
- package/template/.cursor/skills/yedit-table-usage/SKILL.md +1 -1
- package/template/.cursor/skills/yss-api-integration/SKILL.md +1 -1
- package/template/.cursor/skills/yss-audit-log/SKILL.md +1 -1
- package/template/.cursor/skills/yss-cache/SKILL.md +1 -1
- package/template/.cursor/skills/yss-ddd-scaffold-generator/SKILL.md +16 -3
- package/template/.cursor/skills/yss-ddd-scaffold-generator/assets/templates/pom/application-pom.xml.template +1 -1
- package/template/.cursor/skills/yss-ddd-scaffold-generator/assets/templates/pom/parent-pom.xml.template +15 -10
- package/template/.cursor/skills/yss-ddd-scaffold-generator/assets/templates/pom/web-pom.xml.template +3 -3
- package/template/.cursor/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +3 -3
- package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +5 -1
- package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +5 -2
- package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +13 -10
- package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +2 -1
- package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +35 -3
- package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +1 -1
- package/template/.cursor/skills/yss-distributed-id/SKILL.md +1 -1
- package/template/.cursor/skills/yss-dto/SKILL.md +5 -1
- package/template/.cursor/skills/yss-excel-mvc/SKILL.md +1 -1
- package/template/.cursor/skills/yss-exception/SKILL.md +5 -1
- package/template/.cursor/skills/yss-formily-schema-generator/SKILL.md +1 -1
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +14 -6
- package/template/.cursor/skills/yss-implementation-contract-compiler/agents/openai.yaml +1 -1
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/slice-implementation-contract-v2.md +214 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +66 -204
- package/template/.cursor/skills/yss-layered-mvc-scaffold-generator/SKILL.md +15 -2
- package/template/.cursor/skills/yss-layered-mvc-scaffold-generator/scripts/generate_scaffold.mjs +28 -19
- package/template/.cursor/skills/yss-layered-mvc-scaffold-generator/scripts/scaffold-generator.test.mjs +10 -10
- package/template/.cursor/skills/yss-mybatis/SKILL.md +4 -0
- package/template/.cursor/skills/yss-openapi-draft-review/SKILL.md +1 -1
- package/template/.cursor/skills/yss-openapi-governance/SKILL.md +1 -1
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +4 -4
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +29 -7
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +6 -2
- package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +3 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/user-decisions.md +24 -4
- package/template/.cursor/skills/yss-prototype-stage/SKILL.md +1 -1
- package/template/.cursor/skills/yss-repository/SKILL.md +4 -0
- package/template/.cursor/skills/yss-research/SKILL.md +1 -1
- package/template/.cursor/skills/yss-resilience4j/SKILL.md +1 -1
- package/template/.cursor/skills/yss-security-algorithm/SKILL.md +1 -1
- package/template/.cursor/skills/yss-stage-decision/SKILL.md +1 -1
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-contract.md +2 -0
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +3 -3
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +3 -3
- package/template/.cursor/skills/yss-stage-decision/tests/run-scenarios.mjs +33 -0
- package/template/.cursor/skills/yss-tactical-design/SKILL.md +5 -1
- package/template/.cursor/skills/yss-technical-design/SKILL.md +5 -1
- package/template/.cursor/skills/yss-technical-design/references/technical-design-contract.md +1 -1
- package/template/.cursor/skills/yss-technical-design/scripts/validate-technical-design.mjs +2 -2
- package/template/.cursor/skills/yss-technical-design/tests/run-scenarios.mjs +1 -1
- package/template/.cursor/skills/yss-ui/SKILL.md +1 -1
- package/template/.cursor/skills/yss-ui-business-page-generation/SKILL.md +1 -1
- package/template/.cursor/skills/yss-up-springboot3/SKILL.md +1 -1
- package/template/.cursor/skills/yss-use-table-height/SKILL.md +1 -1
- package/template/.cursor/skills/yss-use-tree-height/SKILL.md +1 -1
- package/template/.cursor/skills/yss-userinfo/SKILL.md +1 -1
- package/template/.cursor/skills/yss-validation/SKILL.md +5 -1
- package/template/.cursor/skills/yss-web-controller/SKILL.md +5 -1
- package/template/.cursor/skills/yss-web-controller/scripts/generate_controller.mjs +5 -2
- package/template/.cursor/skills/ytable-usage/SKILL.md +1 -1
- package/template/.cursor/skills/ytree-usage/SKILL.md +1 -1
- package/template/.pi/skills/archify/SKILL.md +1 -1
- package/template/.pi/skills/code-review/SKILL.md +12 -8
- package/template/.pi/skills/file-export-download/SKILL.md +1 -1
- package/template/.pi/skills/frontend-commit/SKILL.md +1 -1
- package/template/.pi/skills/java-backend-commit/SKILL.md +1 -1
- package/template/.pi/skills/llm-wiki/SKILL.md +1 -6
- package/template/.pi/skills/lombok/SKILL.md +10 -336
- package/template/.pi/skills/lombok/references/annotation-examples.md +307 -0
- package/template/.pi/skills/maintaining-skills/SKILL.md +2 -1
- package/template/.pi/skills/maintaining-skills/references/authoring.md +27 -0
- package/template/.pi/skills/mapstruct/SKILL.md +11 -318
- package/template/.pi/skills/mapstruct/references/annotation-examples.md +300 -0
- package/template/.pi/skills/page-form-module/SKILL.md +1 -1
- package/template/.pi/skills/page-skeleton/SKILL.md +1 -1
- package/template/.pi/skills/theme-token-usage/SKILL.md +1 -1
- package/template/.pi/skills/to-tickets/SKILL.md +1 -1
- package/template/.pi/skills/using-git-worktrees/SKILL.md +1 -1
- package/template/.pi/skills/wayfinder/SKILL.md +1 -1
- package/template/.pi/skills/yedit-table-usage/SKILL.md +1 -1
- package/template/.pi/skills/yss-api-integration/SKILL.md +1 -1
- package/template/.pi/skills/yss-audit-log/SKILL.md +1 -1
- package/template/.pi/skills/yss-cache/SKILL.md +1 -1
- package/template/.pi/skills/yss-ddd-scaffold-generator/SKILL.md +16 -3
- package/template/.pi/skills/yss-ddd-scaffold-generator/assets/templates/pom/application-pom.xml.template +1 -1
- package/template/.pi/skills/yss-ddd-scaffold-generator/assets/templates/pom/parent-pom.xml.template +15 -10
- package/template/.pi/skills/yss-ddd-scaffold-generator/assets/templates/pom/web-pom.xml.template +3 -3
- package/template/.pi/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +3 -3
- package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +5 -1
- package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +5 -2
- package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +13 -10
- package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +2 -1
- package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +35 -3
- package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +1 -1
- package/template/.pi/skills/yss-distributed-id/SKILL.md +1 -1
- package/template/.pi/skills/yss-dto/SKILL.md +5 -1
- package/template/.pi/skills/yss-excel-mvc/SKILL.md +1 -1
- package/template/.pi/skills/yss-exception/SKILL.md +5 -1
- package/template/.pi/skills/yss-formily-schema-generator/SKILL.md +1 -1
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +14 -6
- package/template/.pi/skills/yss-implementation-contract-compiler/agents/openai.yaml +1 -1
- package/template/.pi/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/references/slice-implementation-contract-v2.md +214 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +66 -204
- package/template/.pi/skills/yss-layered-mvc-scaffold-generator/SKILL.md +15 -2
- package/template/.pi/skills/yss-layered-mvc-scaffold-generator/scripts/generate_scaffold.mjs +28 -19
- package/template/.pi/skills/yss-layered-mvc-scaffold-generator/scripts/scaffold-generator.test.mjs +10 -10
- package/template/.pi/skills/yss-mybatis/SKILL.md +4 -0
- package/template/.pi/skills/yss-openapi-draft-review/SKILL.md +1 -1
- package/template/.pi/skills/yss-openapi-governance/SKILL.md +1 -1
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +4 -4
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +29 -7
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +6 -2
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +3 -1
- package/template/.pi/skills/yss-product-lifecycle/references/user-decisions.md +24 -4
- package/template/.pi/skills/yss-prototype-stage/SKILL.md +1 -1
- package/template/.pi/skills/yss-repository/SKILL.md +4 -0
- package/template/.pi/skills/yss-research/SKILL.md +1 -1
- package/template/.pi/skills/yss-resilience4j/SKILL.md +1 -1
- package/template/.pi/skills/yss-security-algorithm/SKILL.md +1 -1
- package/template/.pi/skills/yss-stage-decision/SKILL.md +1 -1
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy-contract.md +2 -0
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
- package/template/.pi/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +3 -3
- package/template/.pi/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +3 -3
- package/template/.pi/skills/yss-stage-decision/tests/run-scenarios.mjs +33 -0
- package/template/.pi/skills/yss-tactical-design/SKILL.md +5 -1
- package/template/.pi/skills/yss-technical-design/SKILL.md +5 -1
- package/template/.pi/skills/yss-technical-design/references/technical-design-contract.md +1 -1
- package/template/.pi/skills/yss-technical-design/scripts/validate-technical-design.mjs +2 -2
- package/template/.pi/skills/yss-technical-design/tests/run-scenarios.mjs +1 -1
- package/template/.pi/skills/yss-ui/SKILL.md +1 -1
- package/template/.pi/skills/yss-ui-business-page-generation/SKILL.md +1 -1
- package/template/.pi/skills/yss-up-springboot3/SKILL.md +1 -1
- package/template/.pi/skills/yss-use-table-height/SKILL.md +1 -1
- package/template/.pi/skills/yss-use-tree-height/SKILL.md +1 -1
- package/template/.pi/skills/yss-userinfo/SKILL.md +1 -1
- package/template/.pi/skills/yss-validation/SKILL.md +5 -1
- package/template/.pi/skills/yss-web-controller/SKILL.md +5 -1
- package/template/.pi/skills/yss-web-controller/scripts/generate_controller.mjs +5 -2
- package/template/.pi/skills/ytable-usage/SKILL.md +1 -1
- package/template/.pi/skills/ytree-usage/SKILL.md +1 -1
- package/template/AGENTS.md +1 -0
- package/template/CONTEXT.md +2 -1
- package/template/docs/agents/backend-architecture-profiles.md +1 -1
- package/template/docs/agents/digital-human-roles.yaml +29 -1
- package/template/docs/agents/issue-tracker.md +2 -0
- package/template/docs/architecture/templates/engineering-baseline-review-template.md +6 -6
- package/template/docs/engineering/backend-platforms.json +174 -0
- package/template/docs/engineering/backend-platforms.md +77 -0
- package/template/docs/process/MATT-POCOCK-ENGINEERING-SKILLS.md +1 -0
- package/template/docs/process/existing-ui-baseline.md +1 -1
- package/template/docs/process/harness-process-tailoring.md +9 -1
- package/template/docs/process/lifecycle-artifact-map.md +6 -5
- package/template/docs/process/lifecycle-registry-baseline.json +2 -1
- package/template/docs/process/lifecycle-registry.yaml +17 -8
- package/template/docs/process/schemas/backend-architecture-identity.schema.json +91 -0
- package/template/docs/process/schemas/digital-human-task-package.schema.json +3 -1
- package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +8 -0
- package/template/docs/process/schemas/project-scaffold-contract.schema.json +602 -83
- package/template/docs/process/schemas/review-bundle.schema.json +42 -0
- package/template/docs/process/schemas/scaffold-architecture-decisions.schema.json +323 -39
- package/template/docs/process/schemas/slice-implementation-contract-v3.schema.json +925 -0
- package/template/docs/process/strategic-handoff-package.md +5 -11
- package/template/docs/templates/approval-record-template.yaml +2 -0
- package/template/docs/templates/build-architecture-checklist-template.md +2 -2
- package/template/docs/templates/implementation-routing-template.md +4 -4
- package/template/docs/templates/review-bundle-template.yaml +26 -0
- package/template/docs/templates/vertical-slice-ticket-template.md +22 -31
- package/template/scripts/backend-platforms +14 -0
- package/template/scripts/lib/approval-record.mjs +52 -7
- package/template/scripts/lib/approved-execution-context.mjs +31 -8
- package/template/scripts/lib/backend-architecture.mjs +6 -0
- package/template/scripts/lib/backend-platform-provenance.mjs +67 -0
- package/template/scripts/lib/backend-platform-verification.mjs +210 -0
- package/template/scripts/lib/backend-platform.mjs +175 -0
- package/template/scripts/lib/backend-scaffold-prerequisites.mjs +1 -1
- package/template/scripts/lib/decision-continuation.mjs +79 -0
- package/template/scripts/lib/delivery-preflight.mjs +6 -5
- package/template/scripts/lib/digital-human-roles.mjs +10 -0
- package/template/scripts/lib/frontend-delivery-boundary.mjs +3 -0
- package/template/scripts/lib/implementation-contract-compiler.mjs +42 -9
- package/template/scripts/lib/lifecycle-controls.mjs +2 -2
- package/template/scripts/lib/lifecycle-transition.mjs +3 -1
- package/template/scripts/lib/plan-spec-entry.mjs +3 -3
- package/template/scripts/lib/scaffold-local-database.mjs +1 -0
- package/template/scripts/lib/slice-applicability.mjs +10 -0
- package/template/scripts/lib/slice-contract-preparation.mjs +257 -0
- package/template/scripts/lib/slice-contract-views.mjs +52 -0
- package/template/scripts/lib/slice-contract.mjs +211 -0
- package/template/scripts/lib/slice-repositories.mjs +49 -0
- package/template/scripts/lib/slice-task-package.mjs +66 -0
- package/template/scripts/lib/strategic-handoff-io.mjs +7 -0
- package/template/scripts/lib/strategic-handoff.mjs +33 -8
- package/template/scripts/lib/task-package.mjs +6 -2
- package/template/scripts/lib/technical-design-boundary.mjs +1 -0
- package/template/scripts/lib/user-decision-reuse.mjs +1 -0
- package/template/scripts/lib/user-decision.mjs +17 -3
- package/template/scripts/preflight-delivery +2 -2
- package/template/scripts/slice-contract +30 -0
- package/template/scripts/verify-backend-platform-matrix +69 -0
- package/template/scripts/verify-scaffold-architecture-decisions +5 -0
- package/template/scripts/verify-user-decision +7 -2
- package/template/skills-lock.json +62 -57
- package/template.snapshot.json +3 -3
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { readFile, readdir, writeFile, stat } from "node:fs/promises";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import net from "node:net";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parseXmlDocument } from "../vendor/xml.mjs";
|
|
6
|
+
import { runCommand } from "./command-runner.mjs";
|
|
7
|
+
import { assertJavaPlatform, platformDigest } from "./backend-platform.mjs";
|
|
8
|
+
|
|
9
|
+
const array = value => value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
10
|
+
const fail = message => { throw new TypeError(`platform-dependencies: ${message}`); };
|
|
11
|
+
const gav = item => `${item.groupId}:${item.artifactId}`;
|
|
12
|
+
export async function platformEvidenceArtifacts(evidenceDir) {
|
|
13
|
+
const artifacts = [];
|
|
14
|
+
for (const entry of await readdir(evidenceDir, { withFileTypes: true })) {
|
|
15
|
+
if (entry.isFile() && (entry.name.endsWith('.log') || ['effective-pom.xml', 'boot-bom-effective.xml', 'dependency-trees.json', 'platform-tests.xml'].includes(entry.name))) {
|
|
16
|
+
artifacts.push({ ref: entry.name, digest: platformDigest(await readFile(path.join(evidenceDir, entry.name))) });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return artifacts.sort((a, b) => a.ref.localeCompare(b.ref));
|
|
20
|
+
}
|
|
21
|
+
export function checkPlatformTests(source, capabilities = []) {
|
|
22
|
+
const suite = parseXmlDocument(source).testsuite;
|
|
23
|
+
const cases = array(suite?.testcase);
|
|
24
|
+
const required = ['httpJsonRoundTrip', 'httpValidationRejectsBlank', 'validationAndJsonAutoConfiguration', 'mybatisCanMapVerificationQuery', ...(capabilities.includes('feign-client') ? ['feignCanDecodeJson'] : [])];
|
|
25
|
+
if (!suite || required.some(name => !cases.some(item => item['@_name'] === name && !['failure', 'error', 'skipped'].some(key => Object.hasOwn(item, key))))) fail('integration tests missing, failed or skipped');
|
|
26
|
+
return { status: 'passed', test_count: cases.length };
|
|
27
|
+
}
|
|
28
|
+
export async function verifyPlatformTests(projectRoot, evidenceDir, manifest, startedAt) {
|
|
29
|
+
try {
|
|
30
|
+
const module = `${manifest.project_name}-${manifest.architecture_family === 'domain-driven' ? 'bootstrap' : 'server'}`;
|
|
31
|
+
const reportPath = path.join(projectRoot, module, 'target/surefire-reports', `TEST-${manifest.base_package}.PlatformIntegrationTest.xml`);
|
|
32
|
+
if (!startedAt || (await stat(reportPath)).mtimeMs < startedAt) fail("stale integration test report");
|
|
33
|
+
const source = await readFile(reportPath, 'utf8');
|
|
34
|
+
await writeFile(path.join(evidenceDir, 'platform-tests.xml'), source);
|
|
35
|
+
return checkPlatformTests(source, manifest.module_profile?.requested_capabilities);
|
|
36
|
+
} catch (error) { return { status: 'failed', error: error.message }; }
|
|
37
|
+
}
|
|
38
|
+
export function checkPlatformDependencies(profile, effectivePom, dependencyTrees, { bootBom, mainArtifact, components = {} } = {}) {
|
|
39
|
+
const xml = parseXmlDocument(effectivePom);
|
|
40
|
+
const projects = array(xml.projects?.project ?? xml.project);
|
|
41
|
+
if (!projects.length || !dependencyTrees.length) fail("missing effective POM or dependency tree");
|
|
42
|
+
const main = dependencyTrees.filter(item => gav(item) === mainArtifact);
|
|
43
|
+
if (main.length !== 1) fail("missing or duplicate startup-module runtime tree");
|
|
44
|
+
const nodes = [];
|
|
45
|
+
const visit = item => { if (!["compile", "runtime"].includes(item.scope ?? "compile")) return; nodes.push(item); array(item.children).forEach(visit); };
|
|
46
|
+
array(main[0].children).forEach(visit);
|
|
47
|
+
const bom = parseXmlDocument(bootBom ?? "<missing/>").project;
|
|
48
|
+
if (bom?.groupId !== "org.springframework.boot" || bom.artifactId !== "spring-boot-dependencies" || bom.version !== profile.spring_boot_version) fail("wrong or missing independent Boot BOM");
|
|
49
|
+
const managed = new Map(array(bom.dependencyManagement?.dependencies?.dependency).map(item => [gav(item), item.version]));
|
|
50
|
+
if (!managed.size) fail("empty Boot BOM dependency management");
|
|
51
|
+
for (const item of nodes) {
|
|
52
|
+
const expected = managed.get(gav(item)) ?? components[gav(item)];
|
|
53
|
+
if (/^(com\.baomidou|org\.springdoc|org\.springframework\.cloud|com\.yss\.cloud)$/.test(item.groupId) && !expected) fail(`missing component recipe: ${gav(item)}`);
|
|
54
|
+
if (expected && String(item.version) !== String(expected)) fail(`resolved version differs from BOM/recipe: ${gav(item)}`);
|
|
55
|
+
if (/^(org\.springframework(?:\.boot)?$|org\.apache\.tomcat\.embed|jakarta\.(servlet|validation)|javax\.(servlet|validation)|(?:tools|com\.fasterxml)\.jackson|org\.slf4j|ch\.qos\.logback)/.test(item.groupId) && !expected) fail(`unmanaged platform runtime artifact: ${gav(item)}`);
|
|
56
|
+
}
|
|
57
|
+
const requireArtifact = (group, artifact, match) => {
|
|
58
|
+
const hits = nodes.filter(item => gav(item) === `${group}:${artifact}`);
|
|
59
|
+
if (!hits.length || hits.some(item => !match(String(item.version)))) fail(`${group}:${artifact} absent or incompatible`);
|
|
60
|
+
};
|
|
61
|
+
const line = (version, prefix) => version === prefix || version.startsWith(`${prefix}.`);
|
|
62
|
+
requireArtifact("org.springframework.boot", profile.web_starter, version => version === profile.spring_boot_version);
|
|
63
|
+
requireArtifact("org.springframework", "spring-webmvc", version => line(version, profile.framework_line));
|
|
64
|
+
requireArtifact("org.apache.tomcat.embed", "tomcat-embed-core", version => line(version, profile.tomcat_line));
|
|
65
|
+
requireArtifact("com.baomidou", profile.mybatis_plus_starter, version => /^3\./.test(version));
|
|
66
|
+
requireArtifact(profile.jackson_major === 3 ? "tools.jackson.core" : "com.fasterxml.jackson.core", "jackson-databind", version => version.startsWith(`${profile.jackson_major}.`));
|
|
67
|
+
for (const item of nodes) {
|
|
68
|
+
if (item.groupId === "org.springframework.boot" && String(item.version) !== profile.spring_boot_version) fail(`Boot dependency mismatch: ${gav(item)}`);
|
|
69
|
+
if (item.groupId === "org.springframework" && !line(String(item.version), profile.framework_line)) fail(`Spring dependency mismatch: ${gav(item)}`);
|
|
70
|
+
if (item.groupId === "org.apache.tomcat.embed" && !line(String(item.version), profile.tomcat_line)) fail(`Tomcat dependency mismatch: ${gav(item)}`);
|
|
71
|
+
if (item.groupId === "org.springframework.cloud" && /openfeign/.test(item.artifactId) && !line(String(item.version), profile.openfeign_line)) fail("OpenFeign line mismatch");
|
|
72
|
+
if (item.groupId === "org.springdoc" && !String(item.version).startsWith(`${profile.springdoc_major}.`)) fail("springdoc major mismatch");
|
|
73
|
+
if (profile.validation_namespace === "jakarta" && ["javax.servlet", "javax.validation"].includes(item.groupId)) fail(`legacy Java EE API: ${gav(item)}`);
|
|
74
|
+
if (profile.validation_namespace === "jakarta" && item.groupId === "jakarta.validation" && !String(item.version).startsWith("3.")) fail("Jakarta Validation version mismatch");
|
|
75
|
+
if (item.groupId === "jakarta.servlet" && !line(String(item.version), profile.servlet_version)) fail("Servlet API version mismatch");
|
|
76
|
+
if (profile.validation_namespace === "javax" && item.groupId === "jakarta.validation" && !String(item.version).startsWith("2.")) fail("Boot 2 requires javax Validation classes");
|
|
77
|
+
if (profile.validation_namespace === "javax" && item.groupId === "javax.servlet" && !String(item.version).startsWith("4.")) fail("Boot 2 Servlet mismatch");
|
|
78
|
+
if (item.groupId === "com.baomidou" && /spring-boot[34]-starter|mybatis-plus-boot-starter/.test(item.artifactId) && item.artifactId !== profile.mybatis_plus_starter) fail("mixed MyBatis-Plus starters");
|
|
79
|
+
}
|
|
80
|
+
let bootPluginFound = false;
|
|
81
|
+
for (const project of projects) {
|
|
82
|
+
const target = project.properties?.["maven.compiler.release"] ?? project.properties?.["maven.compiler.target"] ?? project.properties?.["java.version"];
|
|
83
|
+
if (![String(profile.java_version), ...(profile.java_version === 8 ? ["1.8"] : [])].includes(String(target))) fail("effective Java target mismatch");
|
|
84
|
+
for (const plugin of array(project.build?.plugins?.plugin)) if (gav(plugin) === "org.springframework.boot:spring-boot-maven-plugin") {
|
|
85
|
+
bootPluginFound = true;
|
|
86
|
+
if (plugin.version !== profile.spring_boot_version) fail("Boot plugin version mismatch");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!bootPluginFound) fail("Boot plugin absent");
|
|
90
|
+
return { status: "passed", runtime_artifacts: nodes, artifact_count: nodes.length, effective_pom_digest: platformDigest(effectivePom), dependency_tree_digest: platformDigest(dependencyTrees) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function platformCommand(projectRoot, evidenceDir, label, args, environment, options = {}) {
|
|
94
|
+
const stdout_ref = path.join(evidenceDir, `${label}.stdout.log`);
|
|
95
|
+
const stderr_ref = path.join(evidenceDir, `${label}.stderr.log`);
|
|
96
|
+
const executed_at = new Date().toISOString();
|
|
97
|
+
const start = Date.now();
|
|
98
|
+
const result = await runCommand(path.join(projectRoot, "mvnw"), args, { cwd: projectRoot, env: environment, timeoutMs: options.timeoutMs || 180000, signal: options.signal, stdoutFile: stdout_ref, stderrFile: stderr_ref, secrets: [environment.MAVEN_REPO_USERNAME, environment.MAVEN_REPO_PASSWORD], progress: true });
|
|
99
|
+
return { command: `./mvnw ${args.join(" ")}`, exit_code: result.status, termination: result.termination, executed_at, duration_ms: Date.now() - start, stdout_ref, stderr_ref, stdout: result.stdout, stderr: result.stderr };
|
|
100
|
+
}
|
|
101
|
+
export async function verifyPlatformDependencies(projectRoot, evidenceDir, profile, environment, options = {}) {
|
|
102
|
+
const commands = [];
|
|
103
|
+
const run = async (label, args) => {
|
|
104
|
+
const result = await platformCommand(projectRoot, evidenceDir, label, args, environment, options);
|
|
105
|
+
const { stdout, stderr, ...record } = result;
|
|
106
|
+
commands.push(record);
|
|
107
|
+
if (result.exit_code !== 0) fail(`${label} failed; see ${record.stderr_ref}`);
|
|
108
|
+
return result;
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
const java = await run("platform-java", ["-version"]);
|
|
112
|
+
assertJavaPlatform(`${java.stdout}\n${java.stderr}`, { java_version: profile.java_version });
|
|
113
|
+
const pom = path.join(evidenceDir, "effective-pom.xml");
|
|
114
|
+
const bootBom = path.join(evidenceDir, "boot-bom-effective.xml");
|
|
115
|
+
await run("platform-boot-bom", ["org.apache.maven.plugins:maven-help-plugin:3.5.1:effective-pom", `-Dartifact=org.springframework.boot:spring-boot-dependencies:${profile.spring_boot_version}`, `-Doutput=${bootBom}`]);
|
|
116
|
+
await run("platform-effective-pom", ["org.apache.maven.plugins:maven-help-plugin:3.5.1:effective-pom", `-Doutput=${pom}`]);
|
|
117
|
+
await run("platform-dependency-tree", ["org.apache.maven.plugins:maven-dependency-plugin:3.8.1:tree", "-DoutputType=json", "-DoutputFile=.yss/platform-dependencies.json"]);
|
|
118
|
+
const trees = [];
|
|
119
|
+
const visited = new Set();
|
|
120
|
+
for (const directory of [projectRoot, ...(await readdir(projectRoot, { withFileTypes: true })).filter(item => item.isDirectory() && item.name.startsWith(`${path.basename(projectRoot)}-`)).map(item => path.join(projectRoot, item.name))]) {
|
|
121
|
+
const candidates = [directory];
|
|
122
|
+
// DDD adapter contains the nested web module.
|
|
123
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) if (entry.isDirectory() && entry.name.startsWith(`${path.basename(projectRoot)}-`)) candidates.push(path.join(directory, entry.name));
|
|
124
|
+
for (const candidate of candidates) {
|
|
125
|
+
if (visited.has(candidate)) continue;
|
|
126
|
+
visited.add(candidate);
|
|
127
|
+
try { trees.push(JSON.parse(await readFile(path.join(candidate, ".yss/platform-dependencies.json"), "utf8"))); }
|
|
128
|
+
catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
await writeFile(path.join(evidenceDir, "dependency-trees.json"), JSON.stringify(trees, null, 2));
|
|
132
|
+
return { ...checkPlatformDependencies(profile, await readFile(pom, "utf8"), trees, { bootBom: await readFile(bootBom, "utf8"), mainArtifact: `${options.manifest.maven_coordinates.group_id}:${options.manifest.project_name}-${options.manifest.architecture_family === "domain-driven" ? "bootstrap" : "server"}`, components: options.components }), commands };
|
|
133
|
+
} catch (error) { return { status: "failed", error: error.message, commands }; }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function freePort() {
|
|
137
|
+
const server = net.createServer();
|
|
138
|
+
await new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", resolve); });
|
|
139
|
+
const port = server.address().port;
|
|
140
|
+
await new Promise(resolve => server.close(resolve));
|
|
141
|
+
return port;
|
|
142
|
+
}
|
|
143
|
+
export function checkRuntimeArchive(entries, artifacts, { providedLombokVersion } = {}) {
|
|
144
|
+
if (!Array.isArray(artifacts) || !artifacts.length) fail("missing runtime artifact inventory");
|
|
145
|
+
const names = new Set(entries.split(/\r?\n/));
|
|
146
|
+
const expected = new Set();
|
|
147
|
+
for (const item of artifacts.filter(item => (item.type ?? "jar") === "jar")) {
|
|
148
|
+
const name = `BOOT-INF/lib/${item.artifactId}-${item.version}${item.classifier ? `-${item.classifier}` : ""}.jar`;
|
|
149
|
+
expected.add(name);
|
|
150
|
+
if (!names.has(name)) fail(`runtime jar missing ${name}`);
|
|
151
|
+
}
|
|
152
|
+
const bootVersion = artifacts.find(item => item.groupId === "org.springframework.boot")?.version;
|
|
153
|
+
// Boot's repackage goal injects its matching jarmode helper outside the dependency tree.
|
|
154
|
+
for (const name of names) if (name.startsWith("BOOT-INF/lib/") && name.endsWith(".jar") && !expected.has(name)
|
|
155
|
+
&& name !== `BOOT-INF/lib/lombok-${providedLombokVersion}.jar`
|
|
156
|
+
&& !["tools", "layertools"].some(kind => name === `BOOT-INF/lib/spring-boot-jarmode-${kind}-${bootVersion}.jar`)) fail(`unexpected packaged runtime jar: ${name}`);
|
|
157
|
+
return { status: "passed" };
|
|
158
|
+
}
|
|
159
|
+
async function inspectRuntimeJar(jar, evidenceDir, environment, options) {
|
|
160
|
+
const tool = environment.JAVA_HOME ? path.join(environment.JAVA_HOME, "bin/jar") : "jar";
|
|
161
|
+
const stdoutFile = path.join(evidenceDir, "runtime-jar-entries.log");
|
|
162
|
+
const stderrFile = path.join(evidenceDir, "runtime-jar.stderr.log");
|
|
163
|
+
const result = await runCommand(tool, ["tf", jar], { env: environment, timeoutMs: 30000, stdoutFile, stderrFile });
|
|
164
|
+
if (result.status !== 0) fail("cannot inspect packaged runtime jar");
|
|
165
|
+
return checkRuntimeArchive(result.stdout, options.runtimeArtifacts, options);
|
|
166
|
+
}
|
|
167
|
+
export async function verifyPlatformStartup(projectRoot, evidenceDir, manifest, environment, options = {}) {
|
|
168
|
+
const runtimeModule = `${manifest.project_name}-${manifest.architecture_family === "domain-driven" ? "bootstrap" : "server"}`;
|
|
169
|
+
const runtimeTarget = path.join(projectRoot, runtimeModule, "target");
|
|
170
|
+
try {
|
|
171
|
+
const packaged = (await readdir(runtimeTarget)).filter(name => name.endsWith(".jar") && !/-sources|-javadoc|-tests/.test(name));
|
|
172
|
+
if (packaged.length !== 1) fail("expected exactly one production runtime jar");
|
|
173
|
+
await inspectRuntimeJar(path.join(runtimeTarget, packaged[0]), evidenceDir, environment, options);
|
|
174
|
+
} catch (error) { return { status: "failed", error: error.message, commands: [] }; }
|
|
175
|
+
const packaging = await platformCommand(projectRoot, evidenceDir, "platform-local-package", ["-Pscaffold-local", "package", "-DskipTests"], environment, options);
|
|
176
|
+
const { stdout: ignoredOut, stderr: ignoredErr, ...command } = packaging;
|
|
177
|
+
if (packaging.exit_code !== 0) return { status: "failed", error: "local packaging failed", commands: [command] };
|
|
178
|
+
const module = `${manifest.project_name}-${manifest.architecture_family === "domain-driven" ? "bootstrap" : "server"}`;
|
|
179
|
+
const target = path.join(projectRoot, module, "target");
|
|
180
|
+
const jars = (await readdir(target)).filter(name => name.endsWith(".jar") && !/-sources|-javadoc|-tests/.test(name));
|
|
181
|
+
if (jars.length !== 1) return { status: "failed", error: "expected exactly one executable jar", commands: [command] };
|
|
182
|
+
const port = await freePort();
|
|
183
|
+
const java = environment.JAVA_HOME ? path.join(environment.JAVA_HOME, "bin/java") : "java";
|
|
184
|
+
const args = ["-jar", path.join(target, jars[0]), "--spring.profiles.active=scaffold-local", "--server.address=127.0.0.1", `--server.port=${port}`];
|
|
185
|
+
const processStartedAt = new Date().toISOString();
|
|
186
|
+
let output = "", errors = "", started = false, exited = false;
|
|
187
|
+
const child = spawn(java, args, { cwd: projectRoot, env: environment, stdio: ["ignore", "pipe", "pipe"] });
|
|
188
|
+
child.stdout.on("data", data => { output += data; if (/Started .* in /.test(output)) started = true; });
|
|
189
|
+
child.stderr.on("data", data => { errors += data; });
|
|
190
|
+
child.on("error", error => { errors += error.message; exited = true; });
|
|
191
|
+
const closed = new Promise(resolve => child.once("close", code => { exited = true; resolve(code); }));
|
|
192
|
+
let passed = false;
|
|
193
|
+
const deadline = Date.now() + Math.min(options.timeoutMs || 60000, 120000);
|
|
194
|
+
try {
|
|
195
|
+
while (!exited && Date.now() < deadline && !options.signal?.aborted) {
|
|
196
|
+
if (started) {
|
|
197
|
+
try { const response = await fetch(`http://127.0.0.1:${port}/`, { signal: AbortSignal.timeout(1000) }); if (response.status > 0 && response.status < 500) { passed = true; break; } } catch {}
|
|
198
|
+
}
|
|
199
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
200
|
+
}
|
|
201
|
+
} finally {
|
|
202
|
+
if (!exited) child.kill("SIGTERM");
|
|
203
|
+
const killTimer = setTimeout(() => { if (!exited) child.kill("SIGKILL"); }, 3000);
|
|
204
|
+
await closed; clearTimeout(killTimer);
|
|
205
|
+
}
|
|
206
|
+
const redact = text => [environment.MAVEN_REPO_USERNAME, environment.MAVEN_REPO_PASSWORD].filter(Boolean).reduce((value, secret) => value.replaceAll(secret, "[REDACTED]"), text);
|
|
207
|
+
await writeFile(path.join(evidenceDir, "startup.stdout.log"), redact(output));
|
|
208
|
+
await writeFile(path.join(evidenceDir, "startup.stderr.log"), redact(errors));
|
|
209
|
+
return { status: passed ? "passed" : "failed", commands: [command], executed_at: processStartedAt, artifact_digest: platformDigest(await readFile(path.join(target, jars[0]))), stdout_ref: path.join(evidenceDir, "startup.stdout.log"), stderr_ref: path.join(evidenceDir, "startup.stderr.log") };
|
|
210
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { assertSourceFingerprint } from "./backend-platform-provenance.mjs";
|
|
2
|
+
/** Shared, exact backend platform selection. Catalog entries are maintenance-owned. */
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
export const PLATFORM_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
9
|
+
export const PLATFORM_CATALOG_REF = "docs/engineering/backend-platforms.json";
|
|
10
|
+
export const PLATFORM_SKILLS = ["yss-ddd-scaffold-generator", "yss-layered-mvc-scaffold-generator"];
|
|
11
|
+
const fail = (message) => { throw new TypeError(`backend-platform: ${message}`); };
|
|
12
|
+
const stable = (value) => Array.isArray(value) ? value.map(stable) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map(key => [key, stable(value[key])])) : value;
|
|
13
|
+
export const platformDigest = value => `sha256:${createHash("sha256").update(typeof value === "string" || Buffer.isBuffer(value) ? value : JSON.stringify(stable(value ?? null))).digest("hex")}`;
|
|
14
|
+
export function loadBackendPlatforms(root = PLATFORM_ROOT) {
|
|
15
|
+
const value = JSON.parse(readFileSync(path.join(root, PLATFORM_CATALOG_REF), "utf8"));
|
|
16
|
+
if (value.schema_version !== 1 || value.kind !== "backend-platform-catalog" || !Array.isArray(value.profiles) || !Array.isArray(value.compatibility)) fail("invalid catalog");
|
|
17
|
+
if (new Set(value.profiles.map(item => `${item.id}:${item.spring_boot_version}:${item.java_version}`)).size !== value.profiles.length || new Set(value.compatibility.map(item => item.id)).size !== value.compatibility.length) fail("duplicate catalog release/id");
|
|
18
|
+
for (const profile of value.profiles) if (!/^\d+\.\d+\.\d+$/.test(profile.spring_boot_version)) fail("catalog requires exact patch versions");
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export function platformProfile(id, catalog = loadBackendPlatforms(), version) {
|
|
22
|
+
const matches = catalog.profiles.filter(item => item.id === id && (!version || item.spring_boot_version === version));
|
|
23
|
+
if (matches.length > 1) fail("multiple patches: select an exact spring_boot_version");
|
|
24
|
+
const result = matches[0];
|
|
25
|
+
if (!result) fail(`unsupported platform_profile: ${id}`);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
export function platformRecipeDigest(profile, entry) {
|
|
29
|
+
const { candidate_blockers, recommended, ...recipe } = profile;
|
|
30
|
+
return platformDigest({ profile: recipe, entry: { id: entry.id, profile_id: entry.profile_id, spring_boot_version: entry.spring_boot_version, parent: entry.parent, bom: entry.bom, components: entry.components ?? {} } });
|
|
31
|
+
}
|
|
32
|
+
export function platformBinding(profile, entry) {
|
|
33
|
+
return { schema_version: 2, profile_id: profile.id, spring_boot_version: profile.spring_boot_version, java_version: profile.java_version, parent: entry.parent, bom: entry.bom, compatibility_id: entry.id, compatibility_digest: platformRecipeDigest(profile, entry) };
|
|
34
|
+
}
|
|
35
|
+
export function resolveBackendPlatform(binding, { catalog = loadBackendPlatforms(), requireVerified = true, root = PLATFORM_ROOT } = {}) {
|
|
36
|
+
if (!binding || binding.schema_version !== 2) fail("missing platform_configuration v2 (v1 is historical read-only); 生命周期须展示 Boot/Java/YSS 组合并取得用户确认");
|
|
37
|
+
const profile = platformProfile(binding.profile_id, catalog, binding.spring_boot_version);
|
|
38
|
+
if (binding.spring_boot_version !== profile.spring_boot_version || binding.java_version !== profile.java_version) fail("unsupported exact Boot/Java combination (x/latest and Java 8/11 for Boot 3.5/4.1 are rejected)");
|
|
39
|
+
const entry = catalog.compatibility.find(item => item.id === binding.compatibility_id);
|
|
40
|
+
const evidenceReports = [];
|
|
41
|
+
if (!entry || entry.profile_id !== profile.id || entry.spring_boot_version !== profile.spring_boot_version) fail("YSS compatibility entry missing; parent/BOM/starter verification required");
|
|
42
|
+
if (platformDigest(binding) !== platformDigest(platformBinding(profile, entry))) fail("stale compatibility digest or YSS coordinates");
|
|
43
|
+
if (requireVerified) {
|
|
44
|
+
if (entry.status !== "verified") fail(`YSS combination ${entry.id} is not verified`);
|
|
45
|
+
if (!Array.isArray(entry.evidence) || !entry.evidence.length) fail("verified combination lacks evidence");
|
|
46
|
+
for (const item of entry.evidence) {
|
|
47
|
+
if (!item.ref || !item.digest || path.isAbsolute(item.ref) || item.ref.split(/[\\/]/).includes("..")) fail("invalid portable evidence reference");
|
|
48
|
+
const bytes = readFileSync(path.join(root, item.ref));
|
|
49
|
+
if (platformDigest(bytes) !== item.digest) fail("compatibility evidence drift");
|
|
50
|
+
const report = JSON.parse(bytes);
|
|
51
|
+
if (report.verification_scope !== "empty-scaffold") fail("qualification requires empty-scaffold evidence");
|
|
52
|
+
if (report.recipe_digest !== platformRecipeDigest(profile, entry)) fail("evidence recipe drift");
|
|
53
|
+
assertSourceFingerprint(report.source_fingerprint, report.architecture_family);
|
|
54
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(report.generated_tree_digest ?? "")) fail("missing generated tree digest");
|
|
55
|
+
evidenceReports.push(report);
|
|
56
|
+
if (report.status !== "passed" || report.spring_boot_version !== profile.spring_boot_version || report.java_version !== profile.java_version || report.architecture_family !== item.architecture_family || platformDigest(report.parent) !== platformDigest(entry.parent) || platformDigest(report.bom) !== platformDigest(entry.bom)) fail("compatibility evidence identity mismatch");
|
|
57
|
+
for (const phase of ["validate", "test", "package"]) if (!report.commands?.some(cmd => cmd.command === `./mvnw ${phase}` && cmd.exit_code === 0 && cmd.stdout_ref && cmd.stderr_ref && cmd.executed_at)) fail("missing real Maven evidence");
|
|
58
|
+
if (report.dependency_check !== "passed" || report.startup_check !== "passed" || report.integration_tests?.status !== "passed") fail("missing dependency/startup/integration-test evidence");
|
|
59
|
+
const required = ["effective-pom.xml", "dependency-trees.json", "platform-tests.xml", "boot-bom-effective.xml", "runtime-jar-entries.log", "startup.stdout.log", "startup.stderr.log", ...["validate", "test", "package"].flatMap(phase => [`mvnw-${phase}.stdout.log`, `mvnw-${phase}.stderr.log`])];
|
|
60
|
+
if (!Array.isArray(report.evidence_artifacts) || required.some(ref => !report.evidence_artifacts.some(artifact => artifact.ref === ref))) fail("missing portable evidence artifacts");
|
|
61
|
+
for (const artifact of report.evidence_artifacts) {
|
|
62
|
+
if (!artifact.ref || path.isAbsolute(artifact.ref) || artifact.ref.split(/[\\/]/).includes("..")) fail("invalid evidence artifact path");
|
|
63
|
+
if (platformDigest(readFileSync(path.join(root, path.dirname(item.ref), artifact.ref))) !== artifact.digest) fail("evidence artifact drift");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { profile, entry, evidenceReports };
|
|
68
|
+
}
|
|
69
|
+
export function assertContractPlatform(contract, decision, options = {}) {
|
|
70
|
+
const binding = contract.platform_configuration;
|
|
71
|
+
const resolved = resolveBackendPlatform(binding, options);
|
|
72
|
+
if (contract.profiles?.platform !== resolved.profile.id || contract.profiles?.validation_namespace !== resolved.profile.validation_namespace) fail("platform and validation_namespace are inconsistent");
|
|
73
|
+
if (platformDigest(contract.maven_coordinates?.parent) !== platformDigest(binding.parent) || contract.maven_coordinates?.yss_components_version !== binding.bom.version || binding.bom.group_id !== "com.yss.cloud" || binding.bom.artifact_id !== "yss-components-bom") fail("YSS Maven coordinates mismatch");
|
|
74
|
+
if (decision && platformDigest(decision.platform_configuration) !== platformDigest(binding)) fail("decision/contract platform_configuration mismatch");
|
|
75
|
+
if (options.requireVerified !== false && !resolved.entry.evidence?.some(item => item.architecture_family === contract.architecture_family)) fail("architecture has no compatibility evidence");
|
|
76
|
+
for (const capability of contract.module_profile?.requested_capabilities ?? []) if (!resolved.entry.capabilities?.includes(capability)) fail(`unverified capability: ${capability}`);
|
|
77
|
+
if (options.requireVerified !== false && !resolved.evidenceReports.some(report => report.architecture_family === contract.architecture_family && (contract.module_profile?.requested_capabilities ?? []).every(capability => report.verified_capabilities?.includes(capability)))) fail("architecture/capability combination lacks evidence");
|
|
78
|
+
return resolved;
|
|
79
|
+
}
|
|
80
|
+
export function assertPlatformAgreement(left, right) {
|
|
81
|
+
if (platformDigest(left) !== platformDigest(right)) fail("platform_configuration drift between contract and Manifest");
|
|
82
|
+
}
|
|
83
|
+
export function javaMajor(output) {
|
|
84
|
+
const value = output.match(/(?:java|openjdk) version "(?:1\.)?(\d+)|Java version:\s*(?:1\.)?(\d+)/i);
|
|
85
|
+
return value ? Number(value[1] ?? value[2]) : null;
|
|
86
|
+
}
|
|
87
|
+
export function assertJavaPlatform(output, binding) {
|
|
88
|
+
if (javaMajor(output) !== binding.java_version) fail(`JDK mismatch: expected Java ${binding.java_version}`);
|
|
89
|
+
}
|
|
90
|
+
export function platformTemplateVars(profile) {
|
|
91
|
+
return { boot_version: profile.spring_boot_version, java_version: profile.java_version === 8 ? "1.8" : String(profile.java_version), java_range: profile.java_version === 8 ? "[1.8,1.9)" : `[${profile.java_version},${profile.java_version + 1})`, web_starter: profile.web_starter, web_test_starter: profile.web_test_starter, aop_starter: profile.aop_starter, swagger_artifact: profile.swagger_artifact, ...Object.fromEntries(Object.entries(profile.versions).map(([key, value]) => [`${key}_version`, value])) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function platformSmokeTest(basePackage, profile, capabilities = []) {
|
|
95
|
+
const mapper = profile.jackson_major === 3 ? "tools.jackson.databind.json.JsonMapper" : "com.fasterxml.jackson.databind.ObjectMapper";
|
|
96
|
+
return `package ${basePackage};
|
|
97
|
+
|
|
98
|
+
import ${profile.validation_namespace}.validation.Validator;
|
|
99
|
+
import ${profile.validation_namespace}.validation.Valid;
|
|
100
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
101
|
+
import org.springframework.context.annotation.Import;
|
|
102
|
+
import org.springframework.web.bind.annotation.RestController;
|
|
103
|
+
import org.springframework.web.bind.annotation.PostMapping;
|
|
104
|
+
import org.springframework.web.bind.annotation.RequestBody;
|
|
105
|
+
import ${profile.validation_namespace}.validation.constraints.NotBlank;
|
|
106
|
+
import ${mapper};
|
|
107
|
+
import org.apache.ibatis.annotations.Select;
|
|
108
|
+
import org.apache.ibatis.session.SqlSession;
|
|
109
|
+
import org.apache.ibatis.session.SqlSessionFactory;
|
|
110
|
+
import org.junit.jupiter.api.Test;
|
|
111
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
112
|
+
import org.springframework.boot.test.context.SpringBootTest;
|
|
113
|
+
import org.springframework.test.context.ActiveProfiles;
|
|
114
|
+
import static org.junit.jupiter.api.Assertions.*;
|
|
115
|
+
|
|
116
|
+
/** Mechanical integration only: no business endpoint or production storage. */
|
|
117
|
+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|
118
|
+
@Import(PlatformIntegrationTest.ProbeController.class)
|
|
119
|
+
@ActiveProfiles("scaffold-test")
|
|
120
|
+
class PlatformIntegrationTest {
|
|
121
|
+
@Value("\${local.server.port}") int port;
|
|
122
|
+
@Autowired Validator validator;
|
|
123
|
+
public static class ProbePayload {
|
|
124
|
+
@NotBlank private String value;
|
|
125
|
+
public String getValue() { return value; }
|
|
126
|
+
public void setValue(String value) { this.value = value; }
|
|
127
|
+
}
|
|
128
|
+
@RestController
|
|
129
|
+
public static class ProbeController {
|
|
130
|
+
@PostMapping(value = "/__scaffold_probe", consumes = "application/json", produces = "application/json")
|
|
131
|
+
public ProbePayload echo(@Valid @RequestBody ProbePayload body) { return body; }
|
|
132
|
+
}
|
|
133
|
+
@Test void httpJsonRoundTrip() throws Exception { httpProbe("probe", 200); }
|
|
134
|
+
@Test void httpValidationRejectsBlank() throws Exception { httpProbe("", 400); }
|
|
135
|
+
private void httpProbe(String value, int expectedStatus) throws Exception {
|
|
136
|
+
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) new java.net.URL("http://127.0.0.1:" + port + "/__scaffold_probe").openConnection();
|
|
137
|
+
connection.setConnectTimeout(5000); connection.setReadTimeout(5000);
|
|
138
|
+
connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/json"); connection.setDoOutput(true);
|
|
139
|
+
ProbePayload request = new ProbePayload(); request.setValue(value);
|
|
140
|
+
try {
|
|
141
|
+
try (java.io.OutputStream stream = connection.getOutputStream()) { stream.write(mapper.writeValueAsBytes(request)); }
|
|
142
|
+
assertEquals(expectedStatus, connection.getResponseCode());
|
|
143
|
+
if (expectedStatus == 200) {
|
|
144
|
+
assertTrue(connection.getContentType().startsWith("application/json"));
|
|
145
|
+
try (java.io.InputStream stream = connection.getInputStream()) { assertEquals(value, mapper.readValue(stream, ProbePayload.class).getValue()); }
|
|
146
|
+
}
|
|
147
|
+
} finally { connection.disconnect(); }
|
|
148
|
+
}
|
|
149
|
+
@Autowired ${mapper.split(".").at(-1)} mapper;
|
|
150
|
+
@Autowired SqlSessionFactory sqlSessionFactory;
|
|
151
|
+
static final class ValidationProbe { @NotBlank String value = ""; }
|
|
152
|
+
@Test void validationAndJsonAutoConfiguration() throws Exception {
|
|
153
|
+
assertFalse(validator.validate(new ValidationProbe()).isEmpty());
|
|
154
|
+
assertEquals("\\\"probe\\\"", mapper.writeValueAsString("probe"));
|
|
155
|
+
}
|
|
156
|
+
interface ProbeMapper { @Select("SELECT 1") int probe(); }
|
|
157
|
+
@Test void mybatisCanMapVerificationQuery() throws Exception {
|
|
158
|
+
try (SqlSession session = sqlSessionFactory.openSession()) {
|
|
159
|
+
if (!session.getConfiguration().hasMapper(ProbeMapper.class)) session.getConfiguration().addMapper(ProbeMapper.class);
|
|
160
|
+
assertEquals(1, session.getMapper(ProbeMapper.class).probe());
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
${capabilities.includes("feign-client") ? ` @Autowired org.springframework.cloud.openfeign.${profile.java_version === 8 ? "FeignContext" : "FeignClientFactory"} feignFactory;
|
|
164
|
+
@Test void feignCanDecodeJson() throws Exception {
|
|
165
|
+
feign.codec.Decoder decoder = feignFactory.getInstance("platform-probe", feign.codec.Decoder.class);
|
|
166
|
+
assertNotNull(decoder);
|
|
167
|
+
feign.Request request = feign.Request.create(feign.Request.HttpMethod.GET, "http://localhost/probe", java.util.Collections.emptyMap(), (byte[]) null, java.nio.charset.StandardCharsets.UTF_8, null);
|
|
168
|
+
feign.Response response = feign.Response.builder().request(request).status(200).reason("OK")
|
|
169
|
+
.headers(java.util.Collections.singletonMap("Content-Type", java.util.Collections.singletonList("application/json")))
|
|
170
|
+
.body("{}", java.nio.charset.StandardCharsets.UTF_8).build();
|
|
171
|
+
assertTrue(decoder.decode(response, java.util.Map.class) instanceof java.util.Map);
|
|
172
|
+
}
|
|
173
|
+
` : ""}}
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
@@ -102,7 +102,7 @@ export async function validateBackendScaffoldPrerequisites(contract, { contractF
|
|
|
102
102
|
const approvalRef = prerequisites.engineering_contract_approval_ref;
|
|
103
103
|
if (approvalRef !== contract.lifecycle_approval_ref || approvalRef !== contract.approval?.approval_ref) fail("工程合同批准引用与脚手架合同不一致");
|
|
104
104
|
const approvalFile = resolveLocalRef(root, approvalRef, "工程合同批准记录");
|
|
105
|
-
const approval = loadApprovalRecord(approvalFile);
|
|
105
|
+
const approval = loadApprovalRecord(approvalFile, 'gate.engineering-contract-approved');
|
|
106
106
|
validateApprovalRecord(approval, { requireApproved: true, root });
|
|
107
107
|
if (approval.gate_id !== "gate.engineering-contract-approved") fail("工程合同批准记录 gate_id 必须为 gate.engineering-contract-approved");
|
|
108
108
|
if (!Array.isArray(approval.approval_scope) || !approval.approval_scope.includes(contract.contract_id) || !approval.approval_scope.includes(contract.project_name)) fail("工程合同批准范围未覆盖当前合同和项目");
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { decisionIO, decisionDigest, decisionError, assertUserDecisionRequirement } from './user-decision.mjs';
|
|
2
|
+
|
|
3
|
+
export const CONTINUATION_CAPABILITY = 'approved-scope-continuation-v1';
|
|
4
|
+
const fail = message => decisionError('user-decision-continuation-invalid', message);
|
|
5
|
+
const strings = value => Array.isArray(value) && value.length > 0 && value.every(x => typeof x === 'string' && x.trim()) && new Set(value).size === value.length;
|
|
6
|
+
const sameSet = (a, b) => strings(a) && strings(b) && a.length === b.length && a.every(x => b.includes(x));
|
|
7
|
+
const basisFields = ['business_scope', 'acceptance', 'contract_commitments', 'authorization', 'risk_acceptance', 'quality', 'external_commitments'];
|
|
8
|
+
|
|
9
|
+
// A continuation validates an explicit human mandate and an independent current
|
|
10
|
+
// review. It never infers semantic equivalence from whitespace or a boolean flag.
|
|
11
|
+
export function assertDecisionContinuation(requirement, roles, options = {}) {
|
|
12
|
+
const io = decisionIO(options);
|
|
13
|
+
const policy = roles.user_decision_policy.continuation;
|
|
14
|
+
if (policy?.capability !== CONTINUATION_CAPABILITY || !policy.boundaries.includes(requirement.boundary)) fail('未授权的延续边界');
|
|
15
|
+
const proof = io.document(requirement.continuation_ref);
|
|
16
|
+
if (proof.schema_version !== 1 || proof.kind !== CONTINUATION_CAPABILITY || proof.boundary !== requirement.boundary || proof.subject?.ref !== requirement.subject_ref || !sameSet(proof.scope, requirement.scope)) fail('当前边界、资产或范围不匹配');
|
|
17
|
+
const current = (asset, parse = true) => {
|
|
18
|
+
if (!asset?.ref || !asset.version || !asset.digest || decisionDigest(io.bytes(asset.ref)) !== asset.digest) fail(`当前证据缺失或过期: ${asset?.ref}`);
|
|
19
|
+
return parse ? io.document(asset.ref) : null;
|
|
20
|
+
};
|
|
21
|
+
if (!proof.source || proof.source.continuation_ref || proof.source.boundary !== 'delivery-scope') fail('必须引用原始交付范围批准,不能链式扩权');
|
|
22
|
+
const sourceResult = assertUserDecisionRequirement(proof.source, { ...options, rolesDoc: roles });
|
|
23
|
+
const mandate = io.document(proof.source.subject_ref);
|
|
24
|
+
if (mandate.schema_version !== 1 || mandate.kind !== 'delivery-authorization' || !Array.isArray(mandate.targets)) fail('缺少已展示并批准的交付授权清单');
|
|
25
|
+
if (!requirement.scope.every(x => proof.source.scope.includes(x))) fail('超出原始批准范围');
|
|
26
|
+
const matches = mandate.targets.filter(x => x.boundary === requirement.boundary && x.subject_ref === requirement.subject_ref && requirement.scope.every(s => x.scope?.includes(s)));
|
|
27
|
+
if (matches.length !== 1) fail('原授权未唯一覆盖当前边界、资产和范围');
|
|
28
|
+
const target = matches[0];
|
|
29
|
+
if (!basisFields.every(key => strings(target.decision_basis?.[key]))) fail('原授权缺少业务、验收、契约、授权、风险、质量或对外承诺依据');
|
|
30
|
+
if (!Array.isArray(mandate.basis) || !mandate.basis.length) fail('原授权缺少可读取的决定依据');
|
|
31
|
+
for (const asset of mandate.basis) {
|
|
32
|
+
if (!asset.ref || !asset.version || decisionDigest(io.bytes(asset.ref)) !== asset.digest) fail('原授权决定依据变化,须重新决定');
|
|
33
|
+
}
|
|
34
|
+
const external = current(mandate.external_policy);
|
|
35
|
+
if (external.status !== 'confirmed' || !Array.isArray(external.requirements)) fail('项目外部审批要求尚未确认,不能自动延续');
|
|
36
|
+
current(proof.subject, false);
|
|
37
|
+
if (requirement.boundary === 'implementation-scope') {
|
|
38
|
+
const manifest = io.document(proof.subject.ref);
|
|
39
|
+
if (manifest.kind !== 'implementation-scope' || !Array.isArray(manifest.slices)) fail('实施范围清单无效');
|
|
40
|
+
for (const ticket of requirement.scope) {
|
|
41
|
+
const slices = manifest.slices.filter(x => x.ticket_ref === ticket);
|
|
42
|
+
const limit = target.implementation_limits?.[ticket];
|
|
43
|
+
if (slices.length !== 1 || !limit || !sameSet(slices[0].repositories, limit.repositories) || !sameSet(slices[0].allowed_write_paths, limit.allowed_write_paths)) fail('实施仓库或写路径超出原授权边界');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!Array.isArray(proof.basis) || !proof.basis.length) fail('缺少本次实际验证证据');
|
|
47
|
+
for (const asset of proof.basis) {
|
|
48
|
+
if (!asset.ref || !asset.version || decisionDigest(io.bytes(asset.ref)) !== asset.digest) fail('本次验证证据过期');
|
|
49
|
+
}
|
|
50
|
+
const review = current(proof.review);
|
|
51
|
+
const actor = roles.roles.find(x => x.id === review.role_id);
|
|
52
|
+
if (!actor || !roles.runtimes.some(x => x.id === review.runtime_id) || !review.principal_ref || !review.drafter_principal_ref || review.principal_ref === review.drafter_principal_ref) fail('缺少独立专业审查身份');
|
|
53
|
+
const eligible = roles.gate_policy.continuation_reviews?.[requirement.boundary] || roles.gate_policy.dual_digital_human?.find(x => x.gate === requirement.boundary)?.countersigners;
|
|
54
|
+
if (!eligible?.includes(review.role_id)) fail('审查能力不覆盖当前边界');
|
|
55
|
+
if (review.decision !== 'approved' || !['within-approved-scope', 'presentation-only', 'implementation-detail'].includes(review.classification)) fail('未知或实质变化须重新决定');
|
|
56
|
+
if (!Array.isArray(review.material_changes) || review.material_changes.length || !Array.isArray(review.findings)) fail('存在实质变化或未完成影响分析');
|
|
57
|
+
for (const finding of review.findings) {
|
|
58
|
+
if (!finding.id || !finding.reason || !['requirement-violation', 'missing-evidence', 'important-risk', 'suggestion'].includes(finding.kind)) fail('审查发现未分类');
|
|
59
|
+
if (finding.kind === 'suggestion' && !finding.follow_up) fail('非阻断建议缺少待办去向');
|
|
60
|
+
if (finding.kind !== 'suggestion' && finding.status !== 'resolved') fail('未解决的缺陷、证据缺口或重要风险');
|
|
61
|
+
}
|
|
62
|
+
if (review.boundary !== requirement.boundary || !sameSet(review.scope, requirement.scope) || decisionDigest(review.subject) !== decisionDigest(proof.subject) || decisionDigest(review.basis) !== decisionDigest(proof.basis)) fail('审查未绑定当前资产、范围和验证证据');
|
|
63
|
+
if (decisionDigest(review.decision_basis) !== decisionDigest(target.decision_basis)) fail('决定依据有实质变化');
|
|
64
|
+
if (!review.reason?.trim() || !review.comparison?.before || !review.comparison?.after) fail('缺少可审阅的前后差异和等价性说明');
|
|
65
|
+
// Before is an immutable, originally approved snapshot; after is the current candidate.
|
|
66
|
+
const before = review.comparison.before;
|
|
67
|
+
if (!mandate.basis.some(x => decisionDigest(x) === decisionDigest(before)) || decisionDigest(review.comparison.after) !== decisionDigest(proof.subject)) fail('前后差异未绑定原授权快照和当前资产');
|
|
68
|
+
for (const obligation of external.requirements) {
|
|
69
|
+
if (!obligation.id || !strings(obligation.boundaries) || !strings(obligation.principals)) fail('外部审批制度记录不完整');
|
|
70
|
+
if (!obligation.boundaries.includes(requirement.boundary)) continue;
|
|
71
|
+
for (const principal of obligation.principals) {
|
|
72
|
+
const reply = proof.external_decisions?.find(x => x.obligation_id === obligation.id && x.principal_ref === principal);
|
|
73
|
+
if (!reply || reply.requirement?.continuation_ref || reply.requirement?.boundary !== requirement.boundary || reply.requirement?.subject_ref !== requirement.subject_ref || !sameSet(reply.requirement?.scope, requirement.scope)) fail('外部强制审批缺失或范围不符');
|
|
74
|
+
const result = assertUserDecisionRequirement(reply.requirement, { ...options, rolesDoc: roles });
|
|
75
|
+
if (!result.validated.length || result.validated.some(x => x.principal_ref !== principal)) fail('外部审批人与制度要求不符');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { ...sourceResult, continued: true, review_ref: proof.review.ref };
|
|
79
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSliceContract, sourceSliceContract, parseSliceYaml } from './slice-contract.mjs';
|
|
1
2
|
import { assertExistingSliceStructure, createApprovedExecutionContext, verifySliceContractApproval } from './approved-execution-context.mjs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { readFileSync, existsSync } from 'node:fs';
|
|
@@ -46,7 +47,7 @@ function requireBackendCapability(key) {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
/** Read-only: never executes input commands, starts services, unpacks archives or writes receipts. */
|
|
49
|
-
export async function preflightDelivery(input,{stage,root=process.cwd()}={}) {
|
|
50
|
+
export async function preflightDelivery(input,{stage,root=process.cwd(),work_unit_id}={}) {
|
|
50
51
|
try { ensure(STAGES.includes(stage),'未知阶段;必须是 prepare|build|export|accept');schema(input,'docs/process/schemas/delivery-preflight-input.schema.json'); }
|
|
51
52
|
catch(error) { return invalidPreflightInput(error.message); }
|
|
52
53
|
root=path.resolve(root,input.governance_root||'.');
|
|
@@ -78,7 +79,7 @@ export async function preflightDelivery(input,{stage,root=process.cwd()}={}) {
|
|
|
78
79
|
if(input.architecture_identity.source_kind==='existing-registration')ensure(input.architecture_identity.repository_id===input.scope.repository_id&&input.architecture_identity.project_id===input.scope.project_id,'架构身份与预检目标仓库/项目不一致');
|
|
79
80
|
},{source:'architecture_identity'});
|
|
80
81
|
if(valid.has('architecture.identity'))await run('architecture.profile',()=>{ensure(architecture.validateArchitectureIdentity(input.architecture_identity).maturity==='supported','不支持未达到 supported 的 architecture_profile');},{source:'architecture_profile'});
|
|
81
|
-
if(['build','export','accept'].includes(stage)&&assets.slice_contract)await run('slice_contract.execution',()=>{requireBackendCapability('slice_contract');execution=createApprovedExecutionContext(assets.slice_contract,{root});},{from:'build',source:assets.slice_contract.approval_ref||'slice_contract',evidence:[assets.slice_contract.ref,...(assets.slice_contract.approval_ref?[assets.slice_contract.approval_ref]:[])]});
|
|
82
|
+
if(['build','export','accept'].includes(stage)&&assets.slice_contract)await run('slice_contract.execution',()=>{requireBackendCapability('slice_contract');execution=createApprovedExecutionContext(assets.slice_contract,{root,work_unit_id});},{from:'build',source:assets.slice_contract.approval_ref||'slice_contract',evidence:[assets.slice_contract.ref,...(assets.slice_contract.approval_ref?[assets.slice_contract.approval_ref]:[])]});
|
|
82
83
|
const archDeps=['architecture.identity',...['repository_registration','engineering_baseline','manifest'].map(x=>`architecture.${x}`)];
|
|
83
84
|
if(archDeps.every(x=>valid.has(x)))await run('architecture',()=>{
|
|
84
85
|
ensure(typeof architecture.verifyArchitectureEvidence==='function','不支持既有工程原始证据验证,请同步支持该身份版本的模板运行时');
|
|
@@ -143,12 +144,12 @@ async function verifyAsset(key,data,binding,{root,input,documents,assets,executi
|
|
|
143
144
|
await validateTechnicalDesign(data,{root,sliceRef:input.scope.slice_id,readOnly:true,execution});
|
|
144
145
|
await approved(root,binding,['gate.engineering-contract-approved','gate.technical-design-approved']);
|
|
145
146
|
} else if(key==='slice_contract') {
|
|
146
|
-
const contract=data
|
|
147
|
+
const contract=normalizeSliceContract(data,{root});
|
|
147
148
|
assertExistingSliceStructure(contract);
|
|
148
|
-
ensure(contract.schema_version
|
|
149
|
+
ensure([2,3].includes(contract.schema_version)&&contract.status==='approved'&&contract.slice_id===input.scope.slice_id,'Slice 合同版本、批准状态或切片不匹配');
|
|
149
150
|
ensure(binding.id===contract.contract_id&&binding.version===contract.contract_version,'Slice 合同身份与引用不一致');
|
|
150
151
|
const compiler=read(safe(ROOT,'.agents/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml'));
|
|
151
|
-
for(const [section,fields]of Object.entries(compiler.slice_contract_required))for(const field of fields)ensure(Object.hasOwn(section==='root'?contract:contract[section]||{},field),`合同缺少 ${section}.${field}`);
|
|
152
|
+
if(contract.schema_version===2)for(const [section,fields]of Object.entries(compiler.slice_contract_required))for(const field of fields)ensure(Object.hasOwn(section==='root'?contract:contract[section]||{},field),`合同缺少 ${section}.${field}`);
|
|
152
153
|
ensure(contract.readiness.blockers.length===0&&contract.readiness.stale_inputs.length===0,'Slice 合同仍有 blocker 或 stale input');
|
|
153
154
|
const resolution=contract.resolution;
|
|
154
155
|
ensure(resolution.freshness==='current','Slice resolution 非 current');
|
|
@@ -388,6 +388,16 @@ export function validateDigitalHumanRoles(doc, { skillIds, stageIds, gateIds, ar
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
const decisions = doc.user_decision_policy;
|
|
391
|
+
if (decisions?.continuation) {
|
|
392
|
+
const continuation = decisions.continuation;
|
|
393
|
+
if (continuation.capability !== 'approved-scope-continuation-v1' || continuation.source_boundary !== 'delivery-scope' || continuation.unknown_change !== 'blocked' || continuation.external_policy_unconfirmed !== 'no-automatic-continuation') fail('授权延续策略不能省略未知变化或外部制度保护');
|
|
394
|
+
requireStringArray(continuation.boundaries, 'continuation.boundaries');
|
|
395
|
+
for (const boundary of continuation.boundaries) {
|
|
396
|
+
if (!gateIds.has(boundary) && boundary !== 'implementation-scope') fail(`未支持的延续边界: ${boundary}`);
|
|
397
|
+
requireStringArray(policy.continuation_reviews?.[boundary], `${boundary}.continuation_reviews`);
|
|
398
|
+
for (const role of policy.continuation_reviews[boundary]) if (!actors.has(role)) fail(`未知延续审查角色: ${role}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
391
401
|
if (decisions?.schema_version !== 1 || decisions.default_responder !== "requester" || decisions.delegate_requires_requester_evidence !== true) fail("缺少统一用户决定策略");
|
|
392
402
|
requireStringArray(decisions.gates, "user_decision_policy.gates");
|
|
393
403
|
for (const gate of decisions.gates) if (!gateIds.has(gate)) fail(`用户决定引用未知门禁: ${gate}`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSliceContract, sourceSliceContract, parseSliceYaml } from './slice-contract.mjs';
|
|
1
2
|
import { loadDeliveryProfile } from './harness-execution-scope.mjs';
|
|
2
3
|
import { existsSync } from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -8,6 +9,7 @@ import { ROOT, read, safe, ensure, digest } from './strategic-handoff-io.mjs';
|
|
|
8
9
|
// Always re-executes the verifier; a caller-supplied "verified" flag is never evidence.
|
|
9
10
|
export function enforceFrontendDelivery(state={}, {root=ROOT,phase='inputs'}={}) {
|
|
10
11
|
ensure(['preflight','design','contract','inputs','implementation','verification'].includes(phase),`未知前端交付阶段: ${phase}`);
|
|
12
|
+
if(state.schema_version===3)state=normalizeSliceContract(state,{root});
|
|
11
13
|
const profile=loadDeliveryProfile(root);
|
|
12
14
|
let contract;
|
|
13
15
|
const contractRef=state.slice_contract_ref||state.contract?.slice_contract_ref;
|
|
@@ -35,6 +37,7 @@ export function enforceFrontendDelivery(state={}, {root=ROOT,phase='inputs'}={})
|
|
|
35
37
|
const file=path.resolve(root,contractRef);
|
|
36
38
|
if(existsSync(file)) {
|
|
37
39
|
const document=read(file);contract=document.slice_contract||document;
|
|
40
|
+
if(contract.schema_version===3)contract=normalizeSliceContract(contract,{root});
|
|
38
41
|
const direct=contract.frontend?.delivery, resolved=contract.resolution?.frontend_delivery;
|
|
39
42
|
ensure(!direct||!resolved||digest(direct)===digest(resolved),'Slice Contract 前端交付绑定冲突');
|
|
40
43
|
binding=direct||resolved||binding;
|