@thebackstoryis/engineering-with-ai 0.2.9
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/Docs/README.md +50 -0
- package/Docs/adoption/consultancy-and-multi-project-rollout.md +135 -0
- package/Docs/adoption/non-technical-team-guide.md +126 -0
- package/Docs/archaeology-technology-and-hosting-discovery.md +212 -0
- package/Docs/blast-radius-and-impact-routing-guide.md +325 -0
- package/Docs/blueprints/internal-blueprint-catalogue.md +146 -0
- package/Docs/blueprints/maintaining-organisation-blueprints.md +154 -0
- package/Docs/blueprints/validation-and-troubleshooting.md +168 -0
- package/Docs/cli-reference.md +113 -0
- package/Docs/completed-phase-evidence-amendments.md +74 -0
- package/Docs/consultancy-network-rollout-control-plane-guide.md +202 -0
- package/Docs/context-aware-delivery-companion-guide.md +198 -0
- package/Docs/context-aware-delivery-companion-user-guide.md +184 -0
- package/Docs/context-management-and-token-efficiency.md +113 -0
- package/Docs/design-systems/design-system-implementation-guide.md +85 -0
- package/Docs/design-systems/design-system-pack-authoring-guide.md +95 -0
- package/Docs/design-systems/design-system-review-guide.md +51 -0
- package/Docs/design-systems/design-system-user-guide.md +96 -0
- package/Docs/design-systems/product-owner-guide.md +49 -0
- package/Docs/designing-organisation-blueprint-packs.md +384 -0
- package/Docs/developer-delivery-guide.md +224 -0
- package/Docs/error-reporting-guide.md +110 -0
- package/Docs/error-reporting-provider-guide.md +49 -0
- package/Docs/examples/error-report-adapter.md +70 -0
- package/Docs/examples/meeting-review.md +76 -0
- package/Docs/examples/minimal-design-system.md +67 -0
- package/Docs/examples/prototype-review-inputs.md +175 -0
- package/Docs/examples/reproducible-archaeology-depth-example.md +144 -0
- package/Docs/examples/test-scenario-input.md +68 -0
- package/Docs/examples/worked-examples.md +147 -0
- package/Docs/existing-project-onboarding-guide.md +214 -0
- package/Docs/explanation/core-concepts.md +26 -0
- package/Docs/explanation/delivery-workflow.md +48 -0
- package/Docs/governance/governance-team-guide.md +139 -0
- package/Docs/governed-starter-project-materialisation-guide.md +284 -0
- package/Docs/guide-catalogue.md +117 -0
- package/Docs/guided-discovery-facilitator-guide.md +172 -0
- package/Docs/guided-intent-workspace-guide.md +109 -0
- package/Docs/guided-phase-evidence-drafting-guide.md +119 -0
- package/Docs/human-approval-and-assurance-guide.md +146 -0
- package/Docs/knowledge-proposals-implementer-guide.md +106 -0
- package/Docs/knowledge-proposals-user-guide.md +247 -0
- package/Docs/maintainers/context-benchmarks.md +29 -0
- package/Docs/maintainers/contributing.md +58 -0
- package/Docs/maintainers/evidence-depth-acceptance.md +72 -0
- package/Docs/maintainers/verification-walkthroughs.md +104 -0
- package/Docs/meeting-evidence-implementer-guide.md +132 -0
- package/Docs/meeting-evidence-user-guide.md +200 -0
- package/Docs/operations/dashboard-and-delivery-state.md +153 -0
- package/Docs/operations/dashboard-configuration.md +87 -0
- package/Docs/operations/installation-updating-and-entitlements.md +135 -0
- package/Docs/operations/premium-personas-setup.md +76 -0
- package/Docs/operations/troubleshooting-and-recovery.md +205 -0
- package/Docs/organisation-rollout-guide.md +142 -0
- package/Docs/persona-entitlement-provider-guide.md +199 -0
- package/Docs/persona-guided-prototype-iteration.md +129 -0
- package/Docs/personas/organisation-specific-personas.md +103 -0
- package/Docs/personas/persona-authoring-cookbook.md +176 -0
- package/Docs/personas/persona-engagement-ui.md +133 -0
- package/Docs/personas/persona-governance.md +118 -0
- package/Docs/platform-export-analysis-guide.md +336 -0
- package/Docs/policies/governance-owner-guide.md +36 -0
- package/Docs/policies/implementation-guide.md +42 -0
- package/Docs/policies/organisation-policy-design-gates.md +58 -0
- package/Docs/policies/policy-pack-authoring-guide.md +108 -0
- package/Docs/policies/product-owner-guide.md +43 -0
- package/Docs/policies/technical-owner-guide.md +37 -0
- package/Docs/product-owner-guide.md +327 -0
- package/Docs/project-portfolio-orchestration-guide.md +199 -0
- package/Docs/quality/manual-qa-and-acceptance.md +162 -0
- package/Docs/quality/persona-driven-test-scenarios.md +172 -0
- package/Docs/quality/reproducible-archaeology-depth-review-checklist.md +89 -0
- package/Docs/reference/capabilities-and-project-layout.md +678 -0
- package/Docs/reference/cli-and-configuration.md +398 -0
- package/Docs/reference/contributions-api.md +23 -0
- package/Docs/reference/security-adapter-authoring.md +81 -0
- package/Docs/reference/starter-adapter-authoring.md +74 -0
- package/Docs/repository-source-map-guide.md +381 -0
- package/Docs/reproducible-archaeology-and-discovery-depth.md +292 -0
- package/Docs/screen-prototype-creation-guide.md +324 -0
- package/Docs/security-validation-guide.md +353 -0
- package/Docs/solution-readiness-review-guide.md +123 -0
- package/Docs/standards/project-standards-authoring.md +157 -0
- package/Docs/team-hub-guide.md +162 -0
- package/Docs/team-hub-resource-registry-guide.md +167 -0
- package/Docs/tutorials/first-delivery.md +83 -0
- package/Docs/tutorials/first-session.md +62 -0
- package/Docs/using-lifecycle-hooks.md +381 -0
- package/Docs/working-with-personas.md +274 -0
- package/LICENSE +165 -0
- package/README.md +96 -0
- package/agents-src/claude/ewai-security-reviewer.md +15 -0
- package/bin/ewai +5 -0
- package/config/archaeology-record-families.yaml +59 -0
- package/config/delivery-artifacts.yaml +121 -0
- package/config/delivery-stages.yaml +77 -0
- package/config/design-system.schema.json +46 -0
- package/config/error-reporting.schema.json +79 -0
- package/config/evidence-depth.schema.json +53 -0
- package/config/intent.schema.json +90 -0
- package/config/knowledge-proposals-proposal.schema.json +34 -0
- package/config/lifecycle-event.schema.json +68 -0
- package/config/lifecycle-handler.schema.json +45 -0
- package/config/lifecycle-hook-ack.schema.json +19 -0
- package/config/meeting-evidence-candidate.schema.json +102 -0
- package/config/organisation-policy.schema.json +137 -0
- package/config/pack.schema.json +250 -0
- package/config/persona-pack.schema.json +21 -0
- package/config/persona.schema.json +17 -0
- package/config/policy-evaluation.schema.json +77 -0
- package/config/policy-facts.schema.json +140 -0
- package/config/portfolio.schema.json +68 -0
- package/config/project.schema.json +313 -0
- package/config/prototype-iteration.schema.json +128 -0
- package/config/rollout.schema.json +87 -0
- package/config/security-adapter.schema.json +31 -0
- package/config/security-scan-request.schema.json +64 -0
- package/config/security-scan-response.schema.json +52 -0
- package/config/security-validation-policy.schema.json +74 -0
- package/config/starter-source-acknowledgement.schema.json +13 -0
- package/config/starter-source-adapter.schema.json +38 -0
- package/config/starter-source-request.schema.json +61 -0
- package/package.json +77 -0
- package/packs/core/pack.yaml +7 -0
- package/packs/design-systems/default/experience-promise.md +9 -0
- package/packs/design-systems/default/intentional-review.md +10 -0
- package/packs/design-systems/default/interaction-and-entry.md +9 -0
- package/packs/design-systems/default/meaningful-content-and-states.md +9 -0
- package/packs/design-systems/default/pack.yaml +44 -0
- package/packs/design-systems/default/principles.md +10 -0
- package/packs/personas/core/pack.yaml +7 -0
- package/packs/personas/core/personas/archaeologist.md +37 -0
- package/packs/personas/core/personas/end-user.md +17 -0
- package/packs/personas/core/personas/maintainer.md +17 -0
- package/packs/personas/core/personas/operator.md +17 -0
- package/packs/personas/core/personas/specs-knowledge-curator.md +35 -0
- package/packs/technologies/laravel/pack.yaml +30 -0
- package/packs/technologies/laravel-nuxt/pack.yaml +30 -0
- package/packs/technologies/nuxt/pack.yaml +30 -0
- package/packs/technologies/power-platform/pack.yaml +31 -0
- package/packs/technologies/salesforce/pack.yaml +25 -0
- package/public/app.js +4896 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/backstory-icon.png +0 -0
- package/public/dashboard-navigation.js +98 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +789 -0
- package/public/styles.css +2693 -0
- package/public/team-hub/app.js +202 -0
- package/public/team-hub/index.html +79 -0
- package/public/team-hub/styles.css +90 -0
- package/scripts/publication-check.mjs +140 -0
- package/scripts/setup.mjs +21 -0
- package/skills-src/ewai-archaeology/SKILL.md +334 -0
- package/skills-src/ewai-archaeology/agents/openai.yaml +4 -0
- package/skills-src/ewai-archaeology/references/archaeology-contract.md +201 -0
- package/skills-src/ewai-archaeology/references/lifecycle-reconstruction.md +177 -0
- package/skills-src/ewai-archaeology/references/maximum-detail-reconstruction.md +97 -0
- package/skills-src/ewai-archaeology/references/model-routing.md +26 -0
- package/skills-src/ewai-architecture/SKILL.md +108 -0
- package/skills-src/ewai-architecture/agents/openai.yaml +4 -0
- package/skills-src/ewai-architecture/references/architecture-contract.md +176 -0
- package/skills-src/ewai-context/SKILL.md +68 -0
- package/skills-src/ewai-context/agents/openai.yaml +4 -0
- package/skills-src/ewai-context-import/SKILL.md +118 -0
- package/skills-src/ewai-context-import/agents/openai.yaml +4 -0
- package/skills-src/ewai-context-import/references/context-import-contract.md +106 -0
- package/skills-src/ewai-dashboard-configuration/SKILL.md +20 -0
- package/skills-src/ewai-deliver/SKILL.md +122 -0
- package/skills-src/ewai-deliver/references/delivery-evidence.md +92 -0
- package/skills-src/ewai-deliver/references/phase-routing.md +31 -0
- package/skills-src/ewai-design-system-apply/SKILL.md +27 -0
- package/skills-src/ewai-design-system-apply/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-apply/references/application-contract.md +36 -0
- package/skills-src/ewai-design-system-author/SKILL.md +28 -0
- package/skills-src/ewai-design-system-author/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-author/references/authoring-contract.md +38 -0
- package/skills-src/ewai-design-system-review/SKILL.md +26 -0
- package/skills-src/ewai-design-system-review/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-review/references/review-contract.md +40 -0
- package/skills-src/ewai-error-reporting/SKILL.md +46 -0
- package/skills-src/ewai-error-reporting/agents/openai.yaml +4 -0
- package/skills-src/ewai-error-reporting/references/provider-contract.md +74 -0
- package/skills-src/ewai-evidence-depth/SKILL.md +72 -0
- package/skills-src/ewai-evidence-depth/agents/openai.yaml +4 -0
- package/skills-src/ewai-evidence-depth/references/evidence-depth-contract.md +127 -0
- package/skills-src/ewai-intent/SKILL.md +68 -0
- package/skills-src/ewai-intent/agents/openai.yaml +4 -0
- package/skills-src/ewai-intent/references/intent-contract.md +42 -0
- package/skills-src/ewai-knowledge-proposals/SKILL.md +105 -0
- package/skills-src/ewai-knowledge-proposals/agents/openai.yaml +4 -0
- package/skills-src/ewai-knowledge-proposals/references/proposal-contract.md +59 -0
- package/skills-src/ewai-meeting-evidence/SKILL.md +106 -0
- package/skills-src/ewai-meeting-evidence/agents/openai.yaml +4 -0
- package/skills-src/ewai-meeting-evidence/references/candidate-contract.md +64 -0
- package/skills-src/ewai-organisation-policy/SKILL.md +62 -0
- package/skills-src/ewai-organisation-policy/agents/openai.yaml +4 -0
- package/skills-src/ewai-organisation-policy/references/policy-contract.md +94 -0
- package/skills-src/ewai-palace-housekeeping/SKILL.md +55 -0
- package/skills-src/ewai-palace-housekeeping/agents/openai.yaml +4 -0
- package/skills-src/ewai-persona-entitlement/SKILL.md +60 -0
- package/skills-src/ewai-persona-entitlement/agents/openai.yaml +4 -0
- package/skills-src/ewai-phase-evidence/SKILL.md +79 -0
- package/skills-src/ewai-phase-evidence/agents/openai.yaml +4 -0
- package/skills-src/ewai-pipeline/SKILL.md +130 -0
- package/skills-src/ewai-pipeline/agents/openai.yaml +4 -0
- package/skills-src/ewai-pipeline/references/cli.md +86 -0
- package/skills-src/ewai-pipeline/references/specs-contract.md +16 -0
- package/skills-src/ewai-portfolio/SKILL.md +70 -0
- package/skills-src/ewai-portfolio/agents/openai.yaml +4 -0
- package/skills-src/ewai-portfolio/references/portfolio-contract.md +67 -0
- package/skills-src/ewai-project-discovery/SKILL.md +95 -0
- package/skills-src/ewai-project-discovery/agents/openai.yaml +4 -0
- package/skills-src/ewai-project-discovery/references/discovery-contract.md +34 -0
- package/skills-src/ewai-prototype-iteration/SKILL.md +30 -0
- package/skills-src/ewai-prototype-iteration/agents/openai.yaml +4 -0
- package/skills-src/ewai-prototype-iteration/references/review-contract.md +49 -0
- package/skills-src/ewai-retro/SKILL.md +48 -0
- package/skills-src/ewai-retro/agents/openai.yaml +4 -0
- package/skills-src/ewai-retro/references/asset-routing.md +14 -0
- package/skills-src/ewai-rollout/SKILL.md +74 -0
- package/skills-src/ewai-rollout/agents/openai.yaml +4 -0
- package/skills-src/ewai-rollout/references/rollout-contract.md +74 -0
- package/skills-src/ewai-shape-intents/SKILL.md +84 -0
- package/skills-src/ewai-shape-intents/agents/openai.yaml +4 -0
- package/skills-src/ewai-shape-intents/references/intent-mapping-contract.md +109 -0
- package/skills-src/ewai-solution-readiness/SKILL.md +55 -0
- package/skills-src/ewai-solution-readiness/agents/openai.yaml +4 -0
- package/skills-src/ewai-standards-check/SKILL.md +93 -0
- package/skills-src/ewai-standards-check/agents/openai.yaml +4 -0
- package/skills-src/ewai-standards-check/references/report-contract.md +116 -0
- package/skills-src/ewai-test-scenarios/SKILL.md +94 -0
- package/skills-src/ewai-test-scenarios/agents/openai.yaml +4 -0
- package/skills-src/ewai-test-scenarios/references/scenario-contract.md +88 -0
- package/src/afk-worker.mjs +16 -0
- package/src/archaeology.mjs +1333 -0
- package/src/checkin.mjs +261 -0
- package/src/cli.mjs +2427 -0
- package/src/companion-guidance.mjs +257 -0
- package/src/companion-opening.mjs +62 -0
- package/src/companion.mjs +256 -0
- package/src/context.mjs +210 -0
- package/src/dashboard-preferences.mjs +80 -0
- package/src/delivery-artifacts.mjs +204 -0
- package/src/delivery-documents.mjs +248 -0
- package/src/delivery-gates.mjs +317 -0
- package/src/delivery.mjs +1433 -0
- package/src/design-system-application.mjs +291 -0
- package/src/design-system-authoring.mjs +101 -0
- package/src/design-systems.mjs +466 -0
- package/src/discovery.mjs +1314 -0
- package/src/error-reporting.mjs +323 -0
- package/src/evidence-depth.mjs +543 -0
- package/src/execution-state.mjs +243 -0
- package/src/install.mjs +166 -0
- package/src/intent-dependencies.mjs +117 -0
- package/src/intent-maps.mjs +402 -0
- package/src/intents.mjs +747 -0
- package/src/knowledge-proposals.mjs +717 -0
- package/src/launcher.mjs +51 -0
- package/src/meeting-evidence.mjs +703 -0
- package/src/network-rollout.mjs +386 -0
- package/src/organisation-blueprints.mjs +438 -0
- package/src/organisation-policies.mjs +448 -0
- package/src/packs.mjs +44 -0
- package/src/paths.mjs +62 -0
- package/src/persona-entitlements.mjs +438 -0
- package/src/persona-licence-config.mjs +98 -0
- package/src/persona-website-provider.mjs +134 -0
- package/src/persona-zip.mjs +87 -0
- package/src/personas.mjs +159 -0
- package/src/platform-metadata-analysis.mjs +314 -0
- package/src/policy-design-gates.mjs +623 -0
- package/src/policy-gate-integration.mjs +318 -0
- package/src/portfolio.mjs +509 -0
- package/src/power-platform-source-map.mjs +190 -0
- package/src/project.mjs +449 -0
- package/src/prototype-iterations.mjs +730 -0
- package/src/repository-source-map.mjs +603 -0
- package/src/runtime/afk-conductor.mjs +973 -0
- package/src/runtime/context-assembly.mjs +457 -0
- package/src/runtime/context-benchmarks.mjs +115 -0
- package/src/runtime/dashboard-actions.mjs +109 -0
- package/src/runtime/dashboard-handoffs.mjs +149 -0
- package/src/runtime/dashboard-server.mjs +1272 -0
- package/src/runtime/dashboard.mjs +197 -0
- package/src/runtime/database.mjs +789 -0
- package/src/runtime/error-reporting.mjs +581 -0
- package/src/runtime/evidence-depth-workspace.mjs +412 -0
- package/src/runtime/execution-leases.mjs +299 -0
- package/src/runtime/guided-discovery.mjs +350 -0
- package/src/runtime/guided-intents.mjs +517 -0
- package/src/runtime/impact-analysis.mjs +535 -0
- package/src/runtime/intents.mjs +222 -0
- package/src/runtime/knowledge.mjs +86 -0
- package/src/runtime/lifecycle-hooks.mjs +1239 -0
- package/src/runtime/mcp-config.mjs +110 -0
- package/src/runtime/mcp-server.mjs +1885 -0
- package/src/runtime/palace.mjs +362 -0
- package/src/runtime/paths.mjs +58 -0
- package/src/runtime/persona-engagement.mjs +255 -0
- package/src/runtime/phase-contributions.mjs +594 -0
- package/src/runtime/policy-workspace.mjs +170 -0
- package/src/runtime/prototype-iterations.mjs +235 -0
- package/src/runtime/provider-adapters.mjs +163 -0
- package/src/runtime/repository-index.mjs +838 -0
- package/src/runtime/runs.mjs +185 -0
- package/src/runtime/security-validation.mjs +1230 -0
- package/src/runtime/starter-materialisation.mjs +1155 -0
- package/src/runtime/team-hub-client.mjs +479 -0
- package/src/runtime/team-hub-database.mjs +288 -0
- package/src/runtime/team-hub-server.mjs +191 -0
- package/src/runtime/team-hub.mjs +110 -0
- package/src/runtime/tree-sitter-index.mjs +390 -0
- package/src/runtime/version.mjs +1 -0
- package/src/runtime/work.mjs +633 -0
- package/src/salesforce-source-map.mjs +212 -0
- package/src/security-validation-config.mjs +224 -0
- package/src/solution-readiness.mjs +620 -0
- package/src/starter-materialisation-contract.mjs +407 -0
- package/src/task-graph.mjs +544 -0
- package/src/team-hub-resources.mjs +239 -0
- package/src/team-hub.mjs +242 -0
- package/src/test-scenarios.mjs +622 -0
- package/src/validation-config.mjs +289 -0
- package/templates/SPECS/1.Scope/personas/registry.yaml +12 -0
- package/templates/SPECS/5.Strategy/patterns/context-packet.md +119 -0
- package/templates/SPECS/6.Build/_tracker-template.md +16 -0
- package/templates/SPECS/pipeline.yaml +62 -0
- package/templates/discovery-answers.yaml +86 -0
- package/templates/intent-body.md +21 -0
- package/tests/fixtures/context-benchmarks.json +9 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
4
|
+
import { atomicJson, deliveryPaths, isWithin } from './delivery-documents.mjs';
|
|
5
|
+
import { readPolicyBaselineStatus } from './organisation-policies.mjs';
|
|
6
|
+
import { readPolicyEvaluationStatus } from './policy-design-gates.mjs';
|
|
7
|
+
import { readImpactWorkspace } from './runtime/impact-analysis.mjs';
|
|
8
|
+
|
|
9
|
+
export const POLICY_DESIGN_AUTHORITY_NOTICE = 'Organisation Policy Design Gates are design-time evidence only. They do not enforce production traffic, execute production code, certify compliance, approve Build or Manual QA, authorise release, or accept residual risk.';
|
|
10
|
+
|
|
11
|
+
const APPLICABLE_PHASES = new Set(['intent', 'plan']);
|
|
12
|
+
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
13
|
+
const ROUTES = new Set(['claim', 'task', 'automated-test', 'named-human', 'specialist-review']);
|
|
14
|
+
const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/;
|
|
15
|
+
const EMPTY_IMPACT = Object.freeze({ actors: [], aiUse: [], data: [], destinations: [], hosting: [], integrations: [] });
|
|
16
|
+
|
|
17
|
+
function canonical(value) {
|
|
18
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
19
|
+
if (value && typeof value === 'object') {
|
|
20
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function digest(value) {
|
|
26
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex')}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function safeIntentReference(value) {
|
|
30
|
+
const reference = String(value ?? '').trim();
|
|
31
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*$/.test(reference)) {
|
|
32
|
+
throw new Error(`Policy gate requires a project intent reference: ${reference || 'missing'}`);
|
|
33
|
+
}
|
|
34
|
+
return reference;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readJson(path, label) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new Error(`${label} is invalid JSON: ${error.message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function deliveryIntentReference(projectRoot, slug) {
|
|
46
|
+
const paths = deliveryPaths(projectRoot, slug);
|
|
47
|
+
if (!existsSync(paths.statePath)) throw new Error(`Policy gate requires delivery truth for ${slug}`);
|
|
48
|
+
const state = readJson(paths.statePath, 'Delivery state');
|
|
49
|
+
return safeIntentReference(state.intent?.id);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function gateEvidencePath(projectRoot, slug, phase) {
|
|
53
|
+
return resolve(deliveryPaths(projectRoot, slug).gatesRoot, phase, 'organisation-policy-design.json');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function currentImpactDigest(projectRoot, slug, options = {}) {
|
|
57
|
+
if (options.currentImpactDigest != null) {
|
|
58
|
+
const value = String(options.currentImpactDigest);
|
|
59
|
+
if (!DIGEST_PATTERN.test(value)) throw new Error('Current policy-material Impact digest must be SHA-256');
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const workspace = readImpactWorkspace(projectRoot, slug);
|
|
64
|
+
const value = workspace.assessments?.[0]?.policyRelevantDigest;
|
|
65
|
+
return DIGEST_PATTERN.test(String(value ?? '')) ? value : digest(EMPTY_IMPACT);
|
|
66
|
+
} catch {
|
|
67
|
+
return digest(EMPTY_IMPACT);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function authority() {
|
|
72
|
+
return {
|
|
73
|
+
designTimeOnly: true,
|
|
74
|
+
productionEnforcement: false,
|
|
75
|
+
productionCodeExecution: false,
|
|
76
|
+
buildApproved: false,
|
|
77
|
+
manualQaApproved: false,
|
|
78
|
+
complianceCertified: false,
|
|
79
|
+
releaseApproved: false,
|
|
80
|
+
residualRiskAccepted: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function recoveryFor(status, reference) {
|
|
85
|
+
if (status === 'not-evaluated') return [
|
|
86
|
+
`Confirm the current policy design facts for ${reference}.`,
|
|
87
|
+
'Use the deterministic evaluator to evaluate the policy design again.',
|
|
88
|
+
];
|
|
89
|
+
if (status === 'stale') return ['Review the current material Impact changes, reconfirm the affected policy design facts, and evaluate the policy design again with the deterministic evaluator.'];
|
|
90
|
+
if (status === 'invalid') return ['Restore the approved policy baseline and accepted evidence from a trusted project-local source, then reconfirm facts and evaluate again.'];
|
|
91
|
+
if (status === 'deny') return ['Change the proposed design or obtain a current, rule-permitted, bounded exception from the named accountable role.'];
|
|
92
|
+
if (status === 'review-required') return ['Record the required named human review against the exact current evaluation and role.'];
|
|
93
|
+
if (status === 'unassessed') return ['Extend or correct the approved policy baseline so this design is explicitly assessed, then evaluate again.'];
|
|
94
|
+
return ['Resolve the current organisational policy evidence before completing this design gate.'];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function safeProjectFile(projectRoot, reference, label) {
|
|
98
|
+
const value = String(reference ?? '').trim().replaceAll('\\', '/');
|
|
99
|
+
if (!value || isAbsolute(value) || value.includes('\0') || value.split('/').includes('..')) {
|
|
100
|
+
throw new Error(`${label} must be a safe project-relative file`);
|
|
101
|
+
}
|
|
102
|
+
const root = resolve(projectRoot);
|
|
103
|
+
const path = resolve(root, value);
|
|
104
|
+
if (!isWithin(root, path) || !existsSync(path) || !statSync(path).isFile()) throw new Error(`${label} does not identify an existing project file: ${value}`);
|
|
105
|
+
if (!isWithin(realpathSync(root), realpathSync(path))) throw new Error(`${label} may not escape the project through a symbolic link`);
|
|
106
|
+
return relative(root, path).replaceAll('\\', '/');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function traceKey(value) {
|
|
110
|
+
return `${value.ruleId}:${value.controlId}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function controlKey(control) {
|
|
114
|
+
return `${control.ruleId}:${control.id}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function normalisedControls(policyStatus) {
|
|
118
|
+
return (Array.isArray(policyStatus?.controls) ? policyStatus.controls : []).map((control, index) => {
|
|
119
|
+
if (control.id && Array.isArray(control.evidence)) return control;
|
|
120
|
+
const source = String(control.source ?? 'decision');
|
|
121
|
+
const description = String(control.description ?? '').trim();
|
|
122
|
+
return {
|
|
123
|
+
...control,
|
|
124
|
+
id: `${source}-control-${digest({ ruleId: control.ruleId, description, index }).slice(7, 19)}`,
|
|
125
|
+
title: control.title ?? `${source === 'exception' ? 'Compensating' : 'Review'} control`,
|
|
126
|
+
description,
|
|
127
|
+
evidence: ['named-human', 'specialist-review'],
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function policyProjection(policyStatus) {
|
|
133
|
+
return { ...policyStatus, controls: normalisedControls(policyStatus) };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function validatePolicyControlTrace(projectRoot, slug, policyStatus, input = []) {
|
|
137
|
+
const required = normalisedControls(policyStatus);
|
|
138
|
+
if (!Array.isArray(input)) throw new Error('Policy control traces must be an array');
|
|
139
|
+
const byKey = new Map(required.map((control) => [controlKey(control), control]));
|
|
140
|
+
const seen = new Set();
|
|
141
|
+
const traces = input.map((raw, index) => {
|
|
142
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error(`Policy control trace ${index + 1} must be an object`);
|
|
143
|
+
const unexpected = Object.keys(raw).filter((key) => !['ruleId', 'controlId', 'route', 'reference', 'owner'].includes(key));
|
|
144
|
+
if (unexpected.length) throw new Error(`Policy control trace contains unsupported field(s): ${unexpected.join(', ')}`);
|
|
145
|
+
const trace = {
|
|
146
|
+
ruleId: String(raw.ruleId ?? '').trim(),
|
|
147
|
+
controlId: String(raw.controlId ?? '').trim(),
|
|
148
|
+
route: String(raw.route ?? '').trim(),
|
|
149
|
+
reference: String(raw.reference ?? '').trim(),
|
|
150
|
+
...(raw.owner == null ? {} : { owner: String(raw.owner).trim() }),
|
|
151
|
+
};
|
|
152
|
+
const key = traceKey(trace);
|
|
153
|
+
const control = byKey.get(key);
|
|
154
|
+
if (!control) throw new Error(`Policy control trace does not match a required control: ${key}`);
|
|
155
|
+
if (seen.has(key)) throw new Error(`Policy control ${key} has more than one asserted trace; retain one accountable route`);
|
|
156
|
+
seen.add(key);
|
|
157
|
+
if (!ROUTES.has(trace.route) || !control.evidence.includes(trace.route)) {
|
|
158
|
+
throw new Error(`Policy control ${key} does not permit the ${trace.route || 'missing'} route`);
|
|
159
|
+
}
|
|
160
|
+
if (['claim', 'task'].includes(trace.route) && !IDENTIFIER.test(trace.reference)) {
|
|
161
|
+
throw new Error(`Policy control ${key} requires a bounded ${trace.route} identifier`);
|
|
162
|
+
}
|
|
163
|
+
const deliveryRoot = deliveryPaths(projectRoot, slug).deliveryRoot;
|
|
164
|
+
if (trace.route === 'claim') {
|
|
165
|
+
const ledgerPath = resolve(deliveryRoot, 'gates/plan/claim-ledger.json');
|
|
166
|
+
const ledger = existsSync(ledgerPath) ? readJson(ledgerPath, 'Claim Ledger') : {};
|
|
167
|
+
const claims = [...(ledger.implementation_claims ?? []), ...(ledger.claims ?? [])];
|
|
168
|
+
const claim = claims.find(({ id }) => id === trace.reference);
|
|
169
|
+
if (!claim) throw new Error(`Policy control ${key} references an unknown Claim Ledger claim: ${trace.reference}`);
|
|
170
|
+
trace.evidencePath = relative(resolve(projectRoot), ledgerPath).replaceAll('\\', '/');
|
|
171
|
+
trace.evidenceDigest = digest(claim);
|
|
172
|
+
} else if (trace.route === 'task') {
|
|
173
|
+
const graphPath = resolve(deliveryRoot, 'task-graph.json');
|
|
174
|
+
const graph = existsSync(graphPath) ? readJson(graphPath, 'Task graph') : {};
|
|
175
|
+
const task = (graph.tasks ?? []).find(({ id }) => id === trace.reference);
|
|
176
|
+
if (!task) throw new Error(`Policy control ${key} references an unknown bounded task: ${trace.reference}`);
|
|
177
|
+
trace.evidencePath = relative(resolve(projectRoot), graphPath).replaceAll('\\', '/');
|
|
178
|
+
trace.evidenceDigest = digest(task);
|
|
179
|
+
} else {
|
|
180
|
+
trace.reference = safeProjectFile(projectRoot, trace.reference, `Policy control ${key} ${trace.route} evidence`);
|
|
181
|
+
if (trace.route === 'automated-test' && !/(?:^|\/)(?:tests?|specs?)(?:\/|$)|\.(?:test|spec)\.[a-z0-9]+$/i.test(trace.reference)) {
|
|
182
|
+
throw new Error(`Policy control ${key} automated-test evidence must identify a test artefact`);
|
|
183
|
+
}
|
|
184
|
+
if (['named-human', 'specialist-review'].includes(trace.route) && (!trace.owner || trace.owner.length > 160)) {
|
|
185
|
+
throw new Error(`Policy control ${key} ${trace.route} evidence requires a named owner`);
|
|
186
|
+
}
|
|
187
|
+
trace.evidencePath = trace.reference;
|
|
188
|
+
trace.evidenceDigest = digest(readFileSync(resolve(projectRoot, trace.reference), 'utf8'));
|
|
189
|
+
}
|
|
190
|
+
return trace;
|
|
191
|
+
}).sort((left, right) => traceKey(left).localeCompare(traceKey(right)));
|
|
192
|
+
const missing = required.filter((control) => !seen.has(controlKey(control))).map((control) => controlKey(control));
|
|
193
|
+
if (missing.length) throw new Error(`Missing actionable policy control trace(s): ${missing.join(', ')}`);
|
|
194
|
+
return {
|
|
195
|
+
schema: 'ewai.policy-control-trace-validation/v1',
|
|
196
|
+
status: 'pass', required: required.length, traced: traces.length, traces,
|
|
197
|
+
authority: authority(), notices: [POLICY_DESIGN_AUTHORITY_NOTICE],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function policyGateRequirementsForPhase(projectRoot, slug, phase) {
|
|
202
|
+
const baseline = readPolicyBaselineStatus(projectRoot);
|
|
203
|
+
if (baseline.status === 'not-configured' || !APPLICABLE_PHASES.has(phase)) {
|
|
204
|
+
return { schema: 'ewai.policy-phase-requirements/v1', configured: baseline.status !== 'not-configured', status: baseline.status, phase, requirements: [] };
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
schema: 'ewai.policy-phase-requirements/v1', configured: true, status: baseline.status, phase,
|
|
208
|
+
requirements: [{
|
|
209
|
+
id: 'organisation-policy-design',
|
|
210
|
+
artefactPath: `gates/${phase}/organisation-policy-design.json`,
|
|
211
|
+
controlTraceRequired: phase === 'plan',
|
|
212
|
+
}],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function verifyGateDigest(record) {
|
|
217
|
+
const { evidenceDigest, ...body } = record;
|
|
218
|
+
return DIGEST_PATTERN.test(String(evidenceDigest ?? '')) && digest(body) === evidenceDigest;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function readPolicyEvidenceFreshness(projectRoot, intentReference, options = {}) {
|
|
222
|
+
const reference = safeIntentReference(intentReference);
|
|
223
|
+
const slug = String(options.slug ?? reference.split('/')[1]);
|
|
224
|
+
const phase = String(options.phase ?? 'plan');
|
|
225
|
+
const baseline = readPolicyBaselineStatus(projectRoot);
|
|
226
|
+
if (baseline.status === 'not-configured') {
|
|
227
|
+
return { schema: 'ewai.policy-evidence-freshness/v1', status: 'not-configured', blocking: false, drift: [], recovery: [], notices: [POLICY_DESIGN_AUTHORITY_NOTICE] };
|
|
228
|
+
}
|
|
229
|
+
const policy = policyProjection(readPolicyEvaluationStatus(projectRoot, reference, options));
|
|
230
|
+
const path = gateEvidencePath(projectRoot, slug, phase);
|
|
231
|
+
if (!existsSync(path)) {
|
|
232
|
+
return { schema: 'ewai.policy-evidence-freshness/v1', status: 'missing', blocking: true, drift: [{ kind: 'gate-evidence', status: 'missing' }], recovery: recoveryFor(policy.status, reference), policy, notices: [POLICY_DESIGN_AUTHORITY_NOTICE] };
|
|
233
|
+
}
|
|
234
|
+
let accepted;
|
|
235
|
+
try { accepted = readJson(path, 'Organisation policy gate evidence'); } catch (error) {
|
|
236
|
+
return { schema: 'ewai.policy-evidence-freshness/v1', status: 'invalid', blocking: true, drift: [{ kind: 'gate-evidence', status: 'invalid', message: error.message }], recovery: recoveryFor('invalid', reference), policy, notices: [POLICY_DESIGN_AUTHORITY_NOTICE] };
|
|
237
|
+
}
|
|
238
|
+
const drift = [];
|
|
239
|
+
if (accepted.schema !== 'ewai.policy-phase-gate/v1' || accepted.phase !== phase || accepted.intentReference !== reference || !verifyGateDigest(accepted)) {
|
|
240
|
+
drift.push({ kind: 'gate-evidence', status: 'changed' });
|
|
241
|
+
}
|
|
242
|
+
if (accepted.policy?.evaluationDigest !== policy.evaluationDigest) drift.push({ kind: 'evaluation-digest', prepared: accepted.policy?.evaluationDigest ?? null, current: policy.evaluationDigest ?? null });
|
|
243
|
+
if (accepted.policy?.policyDigest !== policy.policyDigest) drift.push({ kind: 'policy-digest', prepared: accepted.policy?.policyDigest ?? null, current: policy.policyDigest ?? null });
|
|
244
|
+
if (accepted.policy?.factsDigest !== policy.factsDigest) drift.push({ kind: 'facts-digest', prepared: accepted.policy?.factsDigest ?? null, current: policy.factsDigest ?? null });
|
|
245
|
+
const impactDigest = currentImpactDigest(projectRoot, slug, options);
|
|
246
|
+
if (accepted.impactDigest !== impactDigest) drift.push({ kind: 'policy-material-impact', prepared: accepted.impactDigest ?? null, current: impactDigest });
|
|
247
|
+
if (phase === 'plan' && accepted.controlTrace?.status === 'pass') {
|
|
248
|
+
try {
|
|
249
|
+
const traceInput = accepted.controlTrace.traces.map(({ ruleId, controlId, route, reference: evidenceReference, owner }) => ({
|
|
250
|
+
ruleId, controlId, route, reference: evidenceReference, ...(owner ? { owner } : {}),
|
|
251
|
+
}));
|
|
252
|
+
const currentTrace = validatePolicyControlTrace(projectRoot, slug, policy, traceInput);
|
|
253
|
+
if (JSON.stringify(currentTrace) !== JSON.stringify(accepted.controlTrace)) drift.push({ kind: 'control-trace-evidence', status: 'changed' });
|
|
254
|
+
} catch (error) {
|
|
255
|
+
drift.push({ kind: 'control-trace-evidence', status: 'invalid', message: error.message });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const stale = drift.length > 0 || policy.blocking === true;
|
|
259
|
+
return {
|
|
260
|
+
schema: 'ewai.policy-evidence-freshness/v1', status: stale ? 'stale' : 'current', blocking: stale,
|
|
261
|
+
drift, recovery: stale ? recoveryFor(drift.some(({ kind }) => kind === 'policy-material-impact') ? 'stale' : policy.status, reference) : [],
|
|
262
|
+
policy, impactDigest, evidencePath: relative(resolve(projectRoot), path).replaceAll('\\', '/'), notices: [POLICY_DESIGN_AUTHORITY_NOTICE],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function evaluatePolicyGate(projectRoot, slug, phase, input = {}, options = {}) {
|
|
267
|
+
const requirements = policyGateRequirementsForPhase(projectRoot, slug, phase);
|
|
268
|
+
if (!requirements.requirements.length) {
|
|
269
|
+
return {
|
|
270
|
+
schema: 'ewai.policy-phase-gate/v1', slug, phase, status: requirements.status,
|
|
271
|
+
configured: false, blocking: false, blockers: [], recovery: [], notices: [POLICY_DESIGN_AUTHORITY_NOTICE], authority: authority(),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const intentReference = deliveryIntentReference(projectRoot, slug);
|
|
275
|
+
const policy = policyProjection(readPolicyEvaluationStatus(projectRoot, intentReference, options));
|
|
276
|
+
const blockers = [];
|
|
277
|
+
if (policy.blocking) blockers.push(policy.notice ?? `Organisation policy status is ${policy.status}.`);
|
|
278
|
+
let controlTrace = null;
|
|
279
|
+
if (!policy.blocking && phase === 'plan') {
|
|
280
|
+
try {
|
|
281
|
+
controlTrace = validatePolicyControlTrace(projectRoot, slug, policy, input.controlTraces ?? []);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
blockers.push(error.message);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const path = gateEvidencePath(projectRoot, slug, phase);
|
|
287
|
+
if (existsSync(path)) {
|
|
288
|
+
const accepted = readJson(path, 'Organisation policy gate evidence');
|
|
289
|
+
const freshness = readPolicyEvidenceFreshness(projectRoot, intentReference, { ...options, slug, phase });
|
|
290
|
+
const impactOnlyDrift = freshness.drift?.some(({ kind }) => kind === 'policy-material-impact')
|
|
291
|
+
&& freshness.policy?.evaluationDigest === accepted.policy?.evaluationDigest;
|
|
292
|
+
const integrityDrift = freshness.drift?.some(({ kind }) => kind === 'gate-evidence');
|
|
293
|
+
if (integrityDrift || impactOnlyDrift) {
|
|
294
|
+
return {
|
|
295
|
+
schema: 'ewai.policy-phase-gate/v1', slug, phase, intentReference, status: freshness.status,
|
|
296
|
+
configured: true, blocking: true, blockers: freshness.drift.map(({ kind }) => `Policy evidence drift: ${kind}`),
|
|
297
|
+
recovery: freshness.recovery, policy, impactDigest: freshness.impactDigest, notices: [POLICY_DESIGN_AUTHORITY_NOTICE], authority: authority(),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const sameTrace = phase !== 'plan'
|
|
301
|
+
|| JSON.stringify(accepted.controlTrace?.traces ?? []) === JSON.stringify(controlTrace?.traces ?? []);
|
|
302
|
+
if (!freshness.blocking && blockers.length === 0 && accepted.blocking === false && sameTrace) return accepted;
|
|
303
|
+
}
|
|
304
|
+
const record = {
|
|
305
|
+
schema: 'ewai.policy-phase-gate/v1', slug, phase, intentReference,
|
|
306
|
+
status: policy.status, configured: true, blocking: blockers.length > 0, blockers,
|
|
307
|
+
recovery: blockers.length
|
|
308
|
+
? phase === 'plan' && blockers.some((item) => /control trace/i.test(item))
|
|
309
|
+
? ['Map every required policy control to an allowed Claim Ledger claim, bounded task, automated test, or named human or specialist evidence route.']
|
|
310
|
+
: recoveryFor(policy.status, intentReference)
|
|
311
|
+
: [],
|
|
312
|
+
policy, impactDigest: currentImpactDigest(projectRoot, slug, options), controlTrace,
|
|
313
|
+
notices: [POLICY_DESIGN_AUTHORITY_NOTICE], authority: authority(),
|
|
314
|
+
};
|
|
315
|
+
record.evidenceDigest = digest(record);
|
|
316
|
+
atomicJson(path, record);
|
|
317
|
+
return record;
|
|
318
|
+
}
|