kibi-cli 0.19.0 → 1.0.1
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/dist/cli-operation-loader.d.ts.map +1 -1
- package/dist/cli-operation-loader.js +53 -20
- package/dist/cli-operation-metadata.d.ts +20 -8
- package/dist/cli-operation-metadata.d.ts.map +1 -1
- package/dist/cli-operation-metadata.js +23 -8
- package/dist/cli-protocol.d.ts.map +1 -1
- package/dist/cli-protocol.js +38 -8
- package/dist/cli-register-foundation.d.ts.map +1 -1
- package/dist/cli-register-foundation.js +14 -2
- package/dist/cli-register-maintenance.d.ts.map +1 -1
- package/dist/cli-register-maintenance.js +26 -4
- package/dist/cli-register-reporting.d.ts.map +1 -1
- package/dist/cli-register-reporting.js +10 -0
- package/dist/cli-register-verification.d.ts +3 -0
- package/dist/cli-register-verification.d.ts.map +1 -0
- package/dist/cli-register-verification.js +16 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/commands/aggregated-checks.d.ts +1 -2
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +14 -9
- package/dist/commands/branch.d.ts +36 -0
- package/dist/commands/branch.d.ts.map +1 -1
- package/dist/commands/branch.js +371 -40
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +20 -67
- package/dist/commands/discovery-shared.d.ts.map +1 -1
- package/dist/commands/discovery-shared.js +10 -19
- package/dist/commands/doctor.d.ts +4 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +259 -24
- package/dist/commands/engine.d.ts.map +1 -1
- package/dist/commands/engine.js +10 -7
- package/dist/commands/gc.d.ts +5 -2
- package/dist/commands/gc.d.ts.map +1 -1
- package/dist/commands/gc.js +140 -71
- package/dist/commands/github-init.d.ts +56 -0
- package/dist/commands/github-init.d.ts.map +1 -0
- package/dist/commands/github-init.js +310 -0
- package/dist/commands/init-helpers.d.ts +1 -1
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +73 -25
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +79 -24
- package/dist/commands/legacy-storage-migration.d.ts +54 -0
- package/dist/commands/legacy-storage-migration.d.ts.map +1 -0
- package/dist/commands/legacy-storage-migration.js +277 -0
- package/dist/commands/migrate.d.ts +7 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +244 -91
- package/dist/commands/report.d.ts +24 -0
- package/dist/commands/report.d.ts.map +1 -0
- package/dist/commands/report.js +191 -0
- package/dist/commands/sync/cache.d.ts +16 -3
- package/dist/commands/sync/cache.d.ts.map +1 -1
- package/dist/commands/sync/cache.js +111 -4
- package/dist/commands/sync/discovery.d.ts +23 -2
- package/dist/commands/sync/discovery.d.ts.map +1 -1
- package/dist/commands/sync/discovery.js +196 -15
- package/dist/commands/sync/extraction.d.ts +8 -1
- package/dist/commands/sync/extraction.d.ts.map +1 -1
- package/dist/commands/sync/extraction.js +38 -4
- package/dist/commands/sync/manifest.d.ts +8 -5
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +142 -39
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +3 -0
- package/dist/commands/sync.d.ts +6 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +304 -79
- package/dist/commands/verify.d.ts +10 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +126 -0
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/engine.d.ts +96 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +392 -23
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +5 -6
- package/dist/extractors/manifest.d.ts +8 -1
- package/dist/extractors/manifest.d.ts.map +1 -1
- package/dist/extractors/manifest.js +51 -9
- package/dist/extractors/markdown.d.ts +2 -1
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +21 -3
- package/dist/extractors/playwright-cases.d.ts +24 -0
- package/dist/extractors/playwright-cases.d.ts.map +1 -0
- package/dist/extractors/playwright-cases.js +126 -0
- package/dist/extractors/relationships.d.ts.map +1 -1
- package/dist/extractors/relationships.js +1 -0
- package/dist/extractors/symbol-coordinates.d.ts +81 -3
- package/dist/extractors/symbol-coordinates.d.ts.map +1 -1
- package/dist/extractors/symbol-coordinates.js +389 -51
- package/dist/extractors/symbols-coordinator.js +11 -1
- package/dist/intent-search.d.ts +60 -0
- package/dist/intent-search.d.ts.map +1 -0
- package/dist/intent-search.js +433 -0
- package/dist/operations/bootstrap/activation.d.ts.map +1 -0
- package/dist/operations/{autopilot → bootstrap}/activation.js +48 -31
- package/dist/operations/{autopilot → bootstrap}/candidate-helpers.d.ts.map +1 -1
- package/dist/operations/bootstrap/candidates.d.ts +4 -0
- package/dist/operations/{autopilot → bootstrap}/candidates.d.ts.map +1 -1
- package/dist/operations/{autopilot → bootstrap}/candidates.js +22 -8
- package/dist/operations/{autopilot → bootstrap}/discovery-evidence.d.ts +2 -2
- package/dist/operations/{autopilot → bootstrap}/discovery-evidence.d.ts.map +1 -1
- package/dist/operations/{autopilot → bootstrap}/discovery-evidence.js +46 -6
- package/dist/operations/{autopilot → bootstrap}/discovery.d.ts +1 -1
- package/dist/operations/bootstrap/discovery.d.ts.map +1 -0
- package/dist/operations/{autopilot → bootstrap}/discovery.js +21 -14
- package/dist/operations/bootstrap/generate.d.ts +8 -0
- package/dist/operations/bootstrap/generate.d.ts.map +1 -0
- package/dist/operations/bootstrap/generate.js +204 -0
- package/dist/operations/{autopilot → bootstrap}/guidance-actions.d.ts +2 -2
- package/dist/operations/{autopilot → bootstrap}/guidance-actions.d.ts.map +1 -1
- package/dist/operations/{autopilot → bootstrap}/guidance-actions.js +4 -4
- package/dist/operations/{autopilot → bootstrap}/guidance.d.ts +2 -2
- package/dist/operations/{autopilot → bootstrap}/guidance.d.ts.map +1 -1
- package/dist/operations/{autopilot → bootstrap}/guidance.js +2 -0
- package/dist/operations/{autopilot → bootstrap}/markdown-candidates.d.ts +2 -2
- package/dist/operations/{autopilot → bootstrap}/markdown-candidates.d.ts.map +1 -1
- package/dist/operations/{autopilot → bootstrap}/markdown-candidates.js +4 -3
- package/dist/operations/bootstrap/presentation.d.ts +15 -0
- package/dist/operations/bootstrap/presentation.d.ts.map +1 -0
- package/dist/operations/bootstrap/presentation.js +177 -0
- package/dist/operations/{autopilot → bootstrap}/requirement-claims.d.ts.map +1 -1
- package/dist/operations/{autopilot → bootstrap}/types.d.ts +63 -21
- package/dist/operations/bootstrap/types.d.ts.map +1 -0
- package/dist/operations/bootstrap/types.js +25 -0
- package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
- package/dist/operations/modeling/model-requirement.js +4 -17
- package/dist/operations/modeling/predicate-applicability.d.ts +16 -0
- package/dist/operations/modeling/predicate-applicability.d.ts.map +1 -0
- package/dist/operations/modeling/predicate-applicability.js +276 -0
- package/dist/operations/modeling/predicate-applyplan.d.ts +13 -2
- package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-applyplan.js +100 -3
- package/dist/operations/modeling/predicate-bindings.d.ts +6 -0
- package/dist/operations/modeling/predicate-bindings.d.ts.map +1 -0
- package/dist/operations/modeling/predicate-bindings.js +122 -0
- package/dist/operations/modeling/predicate-catalog-4.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-catalog-4.js +155 -0
- package/dist/operations/modeling/predicate-inference-1.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-inference-1.js +8 -0
- package/dist/operations/modeling/predicate-inference-launcher.d.ts +10 -0
- package/dist/operations/modeling/predicate-inference-launcher.d.ts.map +1 -0
- package/dist/operations/modeling/predicate-inference-launcher.js +13 -0
- package/dist/operations/modeling/predicate-inference.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-inference.js +4 -0
- package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-loader.js +7 -0
- package/dist/operations/modeling/predicate-ranker.d.ts +8 -1
- package/dist/operations/modeling/predicate-ranker.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-ranker.js +156 -24
- package/dist/operations/modeling/predicate-score-3.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-score-3.js +6 -0
- package/dist/operations/modeling/predicate-types.d.ts +34 -0
- package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-usage-hints-3.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-usage-hints-3.js +40 -0
- package/dist/operations/modeling/predicate-utils.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-utils.js +2 -0
- package/dist/operations/modeling/suggest-predicates.d.ts +1 -1
- package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.js +170 -32
- package/dist/operations/mutation/delete.d.ts.map +1 -1
- package/dist/operations/mutation/delete.js +415 -19
- package/dist/operations/mutation/entity-projection.d.ts +11 -0
- package/dist/operations/mutation/entity-projection.d.ts.map +1 -0
- package/dist/operations/mutation/entity-projection.js +14 -0
- package/dist/operations/mutation/relationships.d.ts +10 -0
- package/dist/operations/mutation/relationships.d.ts.map +1 -1
- package/dist/operations/mutation/relationships.js +71 -1
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +1 -0
- package/dist/operations/mutation/source-authoring.d.ts +61 -0
- package/dist/operations/mutation/source-authoring.d.ts.map +1 -0
- package/dist/operations/mutation/source-authoring.js +543 -0
- package/dist/operations/mutation/symbol-compiler-lock.d.ts +51 -0
- package/dist/operations/mutation/symbol-compiler-lock.d.ts.map +1 -0
- package/dist/operations/mutation/symbol-compiler-lock.js +274 -0
- package/dist/operations/mutation/symbol-granularity.d.ts.map +1 -1
- package/dist/operations/mutation/symbol-granularity.js +34 -3
- package/dist/operations/mutation/symbol-refresh.d.ts +18 -1
- package/dist/operations/mutation/symbol-refresh.d.ts.map +1 -1
- package/dist/operations/mutation/symbol-refresh.js +225 -63
- package/dist/operations/mutation/types.d.ts +57 -1
- package/dist/operations/mutation/types.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +363 -24
- package/dist/operations/mutation/validation.d.ts.map +1 -1
- package/dist/operations/mutation/validation.js +1 -0
- package/dist/operations/planning/apply-plan.d.ts +117 -0
- package/dist/operations/planning/apply-plan.d.ts.map +1 -0
- package/dist/operations/planning/apply-plan.js +1437 -0
- package/dist/operations/planning/compile-intent.d.ts +106 -0
- package/dist/operations/planning/compile-intent.d.ts.map +1 -0
- package/dist/operations/planning/compile-intent.js +562 -0
- package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.js +6 -2
- package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/clauses.js +15 -2
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/ingestion-boundary.js +7 -3
- package/dist/operations/semantic-advisor/predicate-rules-launcher.d.ts +51 -0
- package/dist/operations/semantic-advisor/predicate-rules-launcher.d.ts.map +1 -0
- package/dist/operations/semantic-advisor/predicate-rules-launcher.js +96 -0
- package/dist/operations/semantic-advisor/types.d.ts +13 -5
- package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
- package/dist/operations/verification/ingest-verification.d.ts +29 -0
- package/dist/operations/verification/ingest-verification.d.ts.map +1 -0
- package/dist/operations/verification/ingest-verification.js +192 -0
- package/dist/prolog/codec.d.ts.map +1 -1
- package/dist/prolog/codec.js +2 -1
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +27 -8
- package/dist/public/branch-resolver.d.ts +1 -0
- package/dist/public/branch-resolver.d.ts.map +1 -1
- package/dist/public/branch-resolver.js +1 -0
- package/dist/public/check-types.d.ts +2 -2
- package/dist/public/check-types.d.ts.map +1 -1
- package/dist/public/check-types.js +16 -16
- package/dist/public/diagnostic-usage.d.ts.map +1 -1
- package/dist/public/diagnostic-usage.js +69 -1
- package/dist/public/distribution-parity.d.ts +1 -1
- package/dist/public/distribution-parity.d.ts.map +1 -1
- package/dist/public/distribution-parity.js +1 -0
- package/dist/public/ignore-policy.d.ts.map +1 -1
- package/dist/public/ignore-policy.js +16 -4
- package/dist/public/impact/coverage-depth-quality.d.ts +6 -1
- package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
- package/dist/public/impact/coverage-depth-quality.js +21 -3
- package/dist/public/impact/full-kb-quality.d.ts +4 -2
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
- package/dist/public/impact/full-kb-quality.js +61 -1
- package/dist/public/impact/manifest.d.ts.map +1 -1
- package/dist/public/impact/manifest.js +2 -5
- package/dist/public/operational-artifacts.d.ts.map +1 -1
- package/dist/public/operational-artifacts.js +7 -1
- package/dist/public/operations/catalog.d.ts +682 -67
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/catalog.js +126 -4
- package/dist/public/operations/check-executor.d.ts +2 -2
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +72 -13
- package/dist/public/operations/check-format-shared.d.ts +3 -0
- package/dist/public/operations/check-format-shared.d.ts.map +1 -1
- package/dist/public/operations/check-format-shared.js +3 -0
- package/dist/public/operations/check-helpers.d.ts +27 -2
- package/dist/public/operations/check-helpers.d.ts.map +1 -1
- package/dist/public/operations/check-helpers.js +62 -26
- package/dist/public/operations/contracts.d.ts +11 -0
- package/dist/public/operations/contracts.d.ts.map +1 -0
- package/dist/public/operations/contracts.js +325 -0
- package/dist/public/operations/discovery-executors.d.ts +37 -1
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +206 -6
- package/dist/public/operations/index.d.ts +9 -4
- package/dist/public/operations/index.d.ts.map +1 -1
- package/dist/public/operations/index.js +8 -3
- package/dist/public/operations/migration-plan.d.ts +101 -0
- package/dist/public/operations/migration-plan.d.ts.map +1 -0
- package/dist/public/operations/migration-plan.js +537 -0
- package/dist/public/operations/node-ports.d.ts.map +1 -1
- package/dist/public/operations/node-ports.js +68 -8
- package/dist/public/operations/prolog-json.d.ts +14 -0
- package/dist/public/operations/prolog-json.d.ts.map +1 -1
- package/dist/public/operations/prolog-json.js +44 -5
- package/dist/public/operations/repair-plan.d.ts.map +1 -1
- package/dist/public/operations/repair-plan.js +1 -0
- package/dist/public/operations/result-envelope.d.ts +13 -0
- package/dist/public/operations/result-envelope.d.ts.map +1 -0
- package/dist/public/operations/result-envelope.js +104 -0
- package/dist/public/operations/runtime-types.d.ts +97 -7
- package/dist/public/operations/runtime-types.d.ts.map +1 -1
- package/dist/public/operations/source-relationship-parity.d.ts +13 -0
- package/dist/public/operations/source-relationship-parity.d.ts.map +1 -0
- package/dist/public/operations/source-relationship-parity.js +162 -0
- package/dist/public/operations/specs/{autopilot.d.ts → bootstrap.d.ts} +9 -9
- package/dist/public/operations/specs/bootstrap.d.ts.map +1 -0
- package/dist/public/operations/specs/{autopilot.js → bootstrap.js} +8 -7
- package/dist/public/operations/specs/check.d.ts +4 -4
- package/dist/public/operations/specs/check.d.ts.map +1 -1
- package/dist/public/operations/specs/check.js +4 -3
- package/dist/public/operations/specs/discovery.d.ts +87 -3
- package/dist/public/operations/specs/discovery.d.ts.map +1 -1
- package/dist/public/operations/specs/discovery.js +53 -3
- package/dist/public/operations/specs/modeling.d.ts +3 -3
- package/dist/public/operations/specs/modeling.js +3 -3
- package/dist/public/operations/specs/mutation-schemas.d.ts +190 -47
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +3 -1
- package/dist/public/operations/specs/mutation.d.ts +42 -2
- package/dist/public/operations/specs/mutation.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation.js +26 -2
- package/dist/public/operations/specs/planning.d.ts +270 -0
- package/dist/public/operations/specs/planning.d.ts.map +1 -0
- package/dist/public/operations/specs/planning.js +191 -0
- package/dist/public/operations/specs/reporting.d.ts +4 -1
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +46 -1
- package/dist/public/operations/specs/upsert.d.ts +204 -47
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/operations/specs/upsert.js +9 -0
- package/dist/public/operations/specs/verification.d.ts +78 -0
- package/dist/public/operations/specs/verification.d.ts.map +1 -0
- package/dist/public/operations/specs/verification.js +62 -0
- package/dist/public/operations/symbol-repair-plan.d.ts +11 -0
- package/dist/public/operations/symbol-repair-plan.d.ts.map +1 -0
- package/dist/public/operations/symbol-repair-plan.js +123 -0
- package/dist/public/operations/types.d.ts +52 -1
- package/dist/public/operations/types.d.ts.map +1 -1
- package/dist/public/playwright-case-id.d.ts +8 -0
- package/dist/public/playwright-case-id.d.ts.map +1 -0
- package/dist/public/playwright-case-id.js +25 -0
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +4 -2
- package/dist/public/skills/kibi-bootstrap/SKILL.md +58 -0
- package/dist/public/skills/kibi-bootstrap/resources/bootstrap.md +11 -0
- package/dist/public/skills/kibi-bootstrap/resources/branch-lifecycle.md +7 -0
- package/dist/public/skills/kibi-bootstrap/resources/operation-access.md +5 -0
- package/dist/public/skills/kibi-bootstrap/resources/source-authoring.md +8 -0
- package/dist/public/skills/kibi-freshness/SKILL.md +39 -9
- package/dist/public/skills/kibi-freshness/resources/branch-lifecycle.md +7 -0
- package/dist/public/skills/kibi-freshness/resources/operation-access.md +6 -0
- package/dist/public/skills/kibi-freshness/resources/source-authoring.md +7 -0
- package/dist/public/skills/kibi-traceability/SKILL.md +47 -47
- package/dist/public/skills/kibi-traceability/resources/branch-lifecycle.md +7 -0
- package/dist/public/skills/kibi-traceability/resources/operation-access.md +5 -0
- package/dist/public/skills/kibi-traceability/resources/source-authoring.md +6 -0
- package/dist/public/skills/kibi-traceability/resources/traceability.md +8 -0
- package/dist/public/skills/kibi-usage/SKILL.md +115 -239
- package/dist/public/skills/kibi-usage/resources/branch-lifecycle.md +31 -0
- package/dist/public/skills/kibi-usage/resources/kb-improvement.md +38 -0
- package/dist/public/skills/kibi-usage/resources/operation-access.md +36 -38
- package/dist/public/skills/kibi-usage/resources/relationship-directions.md +3 -3
- package/dist/public/skills/kibi-usage/resources/source-authoring.md +31 -0
- package/dist/public/skills/kibi-usage/resources/workflows.md +56 -3
- package/dist/public/symbol-granularity.d.ts +1 -1
- package/dist/public/symbol-granularity.d.ts.map +1 -1
- package/dist/public/symbol-granularity.js +1 -0
- package/dist/public/telemetry-acceptance.d.ts +7 -0
- package/dist/public/telemetry-acceptance.d.ts.map +1 -1
- package/dist/public/telemetry-acceptance.js +8 -1
- package/dist/public/telemetry-remediation.js +1 -1
- package/dist/public/verification-receipt.d.ts +206 -39
- package/dist/public/verification-receipt.d.ts.map +1 -1
- package/dist/public/verification-receipt.js +171 -4
- package/dist/relationships/shards.d.ts +16 -0
- package/dist/relationships/shards.d.ts.map +1 -1
- package/dist/relationships/shards.js +87 -2
- package/dist/report/brand.d.ts +19 -0
- package/dist/report/brand.d.ts.map +1 -0
- package/dist/report/brand.js +112 -0
- package/dist/report/html-report.d.ts +19 -0
- package/dist/report/html-report.d.ts.map +1 -0
- package/dist/report/html-report.js +963 -0
- package/dist/report/relative-time.d.ts +4 -0
- package/dist/report/relative-time.d.ts.map +1 -0
- package/dist/report/relative-time.js +46 -0
- package/dist/report/report-view.d.ts +17 -0
- package/dist/report/report-view.d.ts.map +1 -0
- package/dist/report/report-view.js +33 -0
- package/dist/report/repository.d.ts +35 -0
- package/dist/report/repository.d.ts.map +1 -0
- package/dist/report/repository.js +151 -0
- package/dist/runtime/cli-runtime.d.ts.map +1 -1
- package/dist/runtime/cli-runtime.js +82 -22
- package/dist/schemas/entity.schema.json +1148 -273
- package/dist/traceability/evidence-model.d.ts +2 -2
- package/dist/traceability/evidence-model.d.ts.map +1 -1
- package/dist/traceability/evidence-model.js +2 -2
- package/dist/traceability/git-staged.d.ts.map +1 -1
- package/dist/traceability/git-staged.js +15 -40
- package/dist/traceability/markdown-validate.d.ts.map +1 -1
- package/dist/traceability/markdown-validate.js +12 -7
- package/dist/traceability/staged-impact-contract.d.ts +3 -3
- package/dist/traceability/staged-impact-contract.d.ts.map +1 -1
- package/dist/traceability/staged-impact-contract.js +10 -38
- package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +8 -3
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +3 -0
- package/dist/types/entities.d.ts +3 -0
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/branch-resolver.d.ts +24 -10
- package/dist/utils/branch-resolver.d.ts.map +1 -1
- package/dist/utils/branch-resolver.js +121 -38
- package/dist/utils/branch-store-locator.d.ts +23 -0
- package/dist/utils/branch-store-locator.d.ts.map +1 -0
- package/dist/utils/branch-store-locator.js +90 -0
- package/dist/utils/branch-store.d.ts +15 -0
- package/dist/utils/branch-store.d.ts.map +1 -0
- package/dist/utils/branch-store.js +130 -0
- package/dist/utils/config.d.ts +39 -37
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +107 -119
- package/dist/utils/kb-manifest.d.ts +35 -0
- package/dist/utils/kb-manifest.d.ts.map +1 -0
- package/dist/utils/kb-manifest.js +154 -0
- package/dist/utils/kb-paths.d.ts +54 -0
- package/dist/utils/kb-paths.d.ts.map +1 -0
- package/dist/utils/kb-paths.js +125 -0
- package/dist/utils/manifest-paths.d.ts +4 -4
- package/dist/utils/manifest-paths.d.ts.map +1 -1
- package/dist/utils/manifest-paths.js +8 -56
- package/dist/utils/rule-registry.d.ts +48 -20
- package/dist/utils/rule-registry.d.ts.map +1 -1
- package/dist/utils/rule-registry.js +82 -77
- package/dist/utils/schema-version.d.ts +5 -5
- package/dist/utils/schema-version.d.ts.map +1 -1
- package/dist/utils/schema-version.js +26 -6
- package/dist/utils/strict-modeling.d.ts +0 -4
- package/dist/utils/strict-modeling.d.ts.map +1 -1
- package/dist/utils/strict-modeling.js +7 -8
- package/dist/verification/playwright-reporter.d.ts +21 -0
- package/dist/verification/playwright-reporter.d.ts.map +1 -0
- package/dist/verification/playwright-reporter.js +114 -0
- package/package.json +46 -7
- package/schema/config.json +2 -2
- package/schema/manifest.json +26 -0
- package/src/public/branch-resolver.ts +1 -0
- package/src/public/check-types.ts +22 -19
- package/src/public/diagnostic-usage.ts +79 -1
- package/src/public/distribution-parity.ts +1 -0
- package/src/public/ignore-policy.ts +16 -10
- package/src/public/impact/coverage-depth-quality.ts +36 -0
- package/src/public/impact/full-kb-quality.ts +118 -5
- package/src/public/impact/manifest.ts +2 -4
- package/src/public/operational-artifacts.ts +9 -1
- package/src/public/operations/catalog.ts +146 -9
- package/src/public/operations/check-executor.ts +102 -15
- package/src/public/operations/check-format-shared.ts +6 -0
- package/src/public/operations/check-helpers.ts +74 -35
- package/src/public/operations/contracts.ts +366 -0
- package/src/public/operations/discovery-executors.ts +299 -12
- package/src/public/operations/index.ts +9 -4
- package/src/public/operations/migration-plan.ts +740 -0
- package/src/public/operations/node-ports.ts +84 -10
- package/src/public/operations/prolog-json.ts +60 -5
- package/src/public/operations/repair-plan.ts +1 -0
- package/src/public/operations/result-envelope.ts +136 -0
- package/src/public/operations/runtime-types.ts +91 -5
- package/src/public/operations/source-relationship-parity.ts +226 -0
- package/src/public/operations/specs/{autopilot.ts → bootstrap.ts} +17 -16
- package/src/public/operations/specs/check.ts +4 -3
- package/src/public/operations/specs/discovery.ts +57 -3
- package/src/public/operations/specs/modeling.ts +3 -3
- package/src/public/operations/specs/mutation-schemas.ts +6 -1
- package/src/public/operations/specs/mutation.ts +27 -2
- package/src/public/operations/specs/planning.ts +234 -0
- package/src/public/operations/specs/reporting.ts +55 -1
- package/src/public/operations/specs/upsert.ts +10 -0
- package/src/public/operations/specs/verification.ts +78 -0
- package/src/public/operations/symbol-repair-plan.ts +164 -0
- package/src/public/operations/types.ts +62 -1
- package/src/public/playwright-case-id.ts +35 -0
- package/src/public/schemas/entity.ts +7 -2
- package/src/public/skills/kibi-bootstrap/SKILL.md +58 -0
- package/src/public/skills/kibi-bootstrap/resources/bootstrap.md +11 -0
- package/src/public/skills/kibi-bootstrap/resources/branch-lifecycle.md +7 -0
- package/src/public/skills/kibi-bootstrap/resources/operation-access.md +5 -0
- package/src/public/skills/kibi-bootstrap/resources/source-authoring.md +8 -0
- package/src/public/skills/kibi-freshness/SKILL.md +39 -9
- package/src/public/skills/kibi-freshness/resources/branch-lifecycle.md +7 -0
- package/src/public/skills/kibi-freshness/resources/operation-access.md +6 -0
- package/src/public/skills/kibi-freshness/resources/source-authoring.md +7 -0
- package/src/public/skills/kibi-traceability/SKILL.md +47 -47
- package/src/public/skills/kibi-traceability/resources/branch-lifecycle.md +7 -0
- package/src/public/skills/kibi-traceability/resources/operation-access.md +5 -0
- package/src/public/skills/kibi-traceability/resources/source-authoring.md +6 -0
- package/src/public/skills/kibi-traceability/resources/traceability.md +8 -0
- package/src/public/skills/kibi-usage/SKILL.md +115 -239
- package/src/public/skills/kibi-usage/resources/branch-lifecycle.md +31 -0
- package/src/public/skills/kibi-usage/resources/kb-improvement.md +38 -0
- package/src/public/skills/kibi-usage/resources/operation-access.md +36 -38
- package/src/public/skills/kibi-usage/resources/relationship-directions.md +3 -3
- package/src/public/skills/kibi-usage/resources/source-authoring.md +31 -0
- package/src/public/skills/kibi-usage/resources/workflows.md +56 -3
- package/src/public/symbol-granularity.ts +1 -0
- package/src/public/telemetry-acceptance.ts +16 -1
- package/src/public/telemetry-remediation.ts +1 -1
- package/src/public/verification-receipt.ts +248 -6
- package/src/schemas/entity.schema.json +1148 -273
- package/templates/github/kibi-badge.yml +86 -0
- package/templates/github/kibi-report.yml +84 -0
- package/dist/operations/autopilot/activation.d.ts.map +0 -1
- package/dist/operations/autopilot/candidates.d.ts +0 -4
- package/dist/operations/autopilot/discovery.d.ts.map +0 -1
- package/dist/operations/autopilot/generate.d.ts +0 -8
- package/dist/operations/autopilot/generate.d.ts.map +0 -1
- package/dist/operations/autopilot/generate.js +0 -112
- package/dist/operations/autopilot/presentation.d.ts +0 -13
- package/dist/operations/autopilot/presentation.d.ts.map +0 -1
- package/dist/operations/autopilot/presentation.js +0 -91
- package/dist/operations/autopilot/types.d.ts.map +0 -1
- package/dist/operations/autopilot/types.js +0 -8
- package/dist/public/operations/specs/autopilot.d.ts.map +0 -1
- package/dist/public/skills/init-kibi/SKILL.md +0 -62
- package/src/public/skills/init-kibi/SKILL.md +0 -62
- /package/dist/operations/{autopilot → bootstrap}/activation.d.ts +0 -0
- /package/dist/operations/{autopilot → bootstrap}/candidate-helpers.d.ts +0 -0
- /package/dist/operations/{autopilot → bootstrap}/candidate-helpers.js +0 -0
- /package/dist/operations/{autopilot → bootstrap}/requirement-claims.d.ts +0 -0
- /package/dist/operations/{autopilot → bootstrap}/requirement-claims.js +0 -0
|
@@ -0,0 +1,1437 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { branchEnsureCommand, branchMigrateCommand, branchRecoverCommand, } from "../../commands/branch.js";
|
|
5
|
+
import { migrateCommand } from "../../commands/migrate.js";
|
|
6
|
+
import { syncCommand } from "../../commands/sync.js";
|
|
7
|
+
import { executeStatus } from "../../public/operations/discovery-executors.js";
|
|
8
|
+
import { migrationPlanHash, } from "../../public/operations/migration-plan.js";
|
|
9
|
+
import { readMigrationConfigStatus } from "../../public/operations/migration-plan.js";
|
|
10
|
+
import { readWorkspaceSnapshot } from "../../public/operations/workspace-snapshot.js";
|
|
11
|
+
import { isDerivedKbPath } from "../../utils/kb-paths.js";
|
|
12
|
+
import { bootstrapEmptyKbSnapshotId, bootstrapPlanHash, } from "../bootstrap/types.js";
|
|
13
|
+
import { executeDelete } from "../mutation/delete.js";
|
|
14
|
+
import { writePendingSourceReceipt } from "../mutation/source-authoring.js";
|
|
15
|
+
import { executeUpsert } from "../mutation/upsert.js";
|
|
16
|
+
import { compilePlanHash, } from "./compile-intent.js";
|
|
17
|
+
// implements REQ-kibi-change-to-proof-plan-compiler
|
|
18
|
+
export const PLAN_APPLY_RESULT_VERSION = "kibi.plan-apply-result.v1";
|
|
19
|
+
const ENTITY_TYPES = new Set([
|
|
20
|
+
"req",
|
|
21
|
+
"scenario",
|
|
22
|
+
"test",
|
|
23
|
+
"adr",
|
|
24
|
+
"flag",
|
|
25
|
+
"event",
|
|
26
|
+
"symbol",
|
|
27
|
+
"fact",
|
|
28
|
+
]);
|
|
29
|
+
function text(value) {
|
|
30
|
+
return typeof value === "string" ? value.trim() : "";
|
|
31
|
+
}
|
|
32
|
+
function isRecord(value) {
|
|
33
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
function digest(value) {
|
|
36
|
+
return createHash("sha256").update(value).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
function relationships(step) {
|
|
39
|
+
if (!Array.isArray(step.relationships))
|
|
40
|
+
return [];
|
|
41
|
+
return step.relationships.filter(isRecord).map((relationship) => {
|
|
42
|
+
const type = text(relationship.type);
|
|
43
|
+
const from = text(relationship.from);
|
|
44
|
+
const to = text(relationship.to);
|
|
45
|
+
if (!type || !from || !to)
|
|
46
|
+
throw new Error("Apply plan failed: every relationship needs type, from, and to");
|
|
47
|
+
return { type, from, to };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function asUpsert(step) {
|
|
51
|
+
const type = text(step.type);
|
|
52
|
+
const id = text(step.id);
|
|
53
|
+
if (!ENTITY_TYPES.has(type))
|
|
54
|
+
throw new Error(`Apply plan failed: unsupported step entity type '${type}'`);
|
|
55
|
+
if (!id)
|
|
56
|
+
throw new Error("Apply plan failed: every step needs an entity id");
|
|
57
|
+
const properties = isRecord(step.properties) ? step.properties : {};
|
|
58
|
+
const document = isRecord(step.document)
|
|
59
|
+
? {
|
|
60
|
+
...(typeof step.document.path === "string"
|
|
61
|
+
? { path: step.document.path }
|
|
62
|
+
: {}),
|
|
63
|
+
...(typeof step.document.body === "string"
|
|
64
|
+
? { body: step.document.body }
|
|
65
|
+
: {}),
|
|
66
|
+
}
|
|
67
|
+
: undefined;
|
|
68
|
+
return {
|
|
69
|
+
type,
|
|
70
|
+
id,
|
|
71
|
+
properties,
|
|
72
|
+
relationships: relationships(step),
|
|
73
|
+
...(document !== undefined ? { document } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function validateCompilePlanShape(args) {
|
|
77
|
+
if (!isRecord(args.plan))
|
|
78
|
+
throw new Error("Apply plan failed: plan must be an object");
|
|
79
|
+
if (args.plan.version !== "kibi.compile-plan.v1")
|
|
80
|
+
throw new Error("Apply plan failed: unsupported plan version");
|
|
81
|
+
if (args.plan.status !== "ready")
|
|
82
|
+
throw new Error("Apply plan failed: only ready plans may be applied");
|
|
83
|
+
if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash))
|
|
84
|
+
throw new Error("Apply plan failed: approvedPlanHash must be a SHA-256 hash");
|
|
85
|
+
if (args.approvedPlanHash !== args.plan.planHash)
|
|
86
|
+
throw new Error("Apply plan failed: approvedPlanHash does not match plan.planHash");
|
|
87
|
+
if (compilePlanHash(args.plan) !==
|
|
88
|
+
args.plan.planHash)
|
|
89
|
+
throw new Error("Apply plan failed: planHash does not match the canonical plan body");
|
|
90
|
+
if (!Array.isArray(args.plan.steps) || args.plan.steps.length === 0)
|
|
91
|
+
throw new Error("Apply plan failed: ready plans must contain at least one step");
|
|
92
|
+
}
|
|
93
|
+
function isMigrationApplyArgs(args) {
|
|
94
|
+
return "plan" in args && args.plan.version === "kibi.migration-plan.v2";
|
|
95
|
+
}
|
|
96
|
+
function isBootstrapApplyArgs(args) {
|
|
97
|
+
return "plan" in args && args.plan.version === "kibi.bootstrap-plan.v1";
|
|
98
|
+
}
|
|
99
|
+
export function orderBootstrapActions(actions, completed = new Set()) {
|
|
100
|
+
const byId = new Map();
|
|
101
|
+
for (const action of actions) {
|
|
102
|
+
if (!action.id || byId.has(action.id))
|
|
103
|
+
throw new Error("Bootstrap apply failed: action IDs must be unique");
|
|
104
|
+
if (action.kind !== "upsert" || !isRecord(action.payload))
|
|
105
|
+
throw new Error(`Bootstrap apply failed: action '${action.id}' is invalid`);
|
|
106
|
+
byId.set(action.id, action);
|
|
107
|
+
}
|
|
108
|
+
const result = [];
|
|
109
|
+
const visiting = new Set();
|
|
110
|
+
const visited = new Set();
|
|
111
|
+
const visit = (action) => {
|
|
112
|
+
if (visited.has(action.id))
|
|
113
|
+
return;
|
|
114
|
+
if (completed.has(action.id)) {
|
|
115
|
+
visited.add(action.id);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (visiting.has(action.id))
|
|
119
|
+
throw new Error(`Bootstrap apply failed: dependency cycle at '${action.id}'`);
|
|
120
|
+
visiting.add(action.id);
|
|
121
|
+
for (const dependency of action.dependsOn ?? []) {
|
|
122
|
+
if (completed.has(dependency))
|
|
123
|
+
continue;
|
|
124
|
+
const endpoint = byId.get(dependency);
|
|
125
|
+
if (!endpoint)
|
|
126
|
+
throw new Error(`Bootstrap apply failed: action '${action.id}' requires missing dependency '${dependency}'`);
|
|
127
|
+
visit(endpoint);
|
|
128
|
+
}
|
|
129
|
+
visiting.delete(action.id);
|
|
130
|
+
visited.add(action.id);
|
|
131
|
+
result.push(action);
|
|
132
|
+
};
|
|
133
|
+
for (const action of [...actions].sort((left, right) => left.id.localeCompare(right.id)))
|
|
134
|
+
visit(action);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
function validateBootstrapPlanShape(args) {
|
|
138
|
+
const { plan } = args;
|
|
139
|
+
if (plan.status !== "ready")
|
|
140
|
+
throw new Error("Bootstrap apply failed: only ready plans may be applied");
|
|
141
|
+
if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash))
|
|
142
|
+
throw new Error("Bootstrap apply failed: approvedPlanHash must be SHA-256");
|
|
143
|
+
if (args.approvedPlanHash !== plan.planHash)
|
|
144
|
+
throw new Error("Bootstrap apply failed: approvedPlanHash does not match planHash");
|
|
145
|
+
if (bootstrapPlanHash(plan) !==
|
|
146
|
+
plan.planHash)
|
|
147
|
+
throw new Error("Bootstrap apply failed: planHash does not match canonical plan body");
|
|
148
|
+
if (plan.activation.applyBlocked)
|
|
149
|
+
throw new Error("Bootstrap apply failed: activation policy blocks application");
|
|
150
|
+
if (!plan.expected.branch ||
|
|
151
|
+
plan.expected.branch === "unknown" ||
|
|
152
|
+
plan.expected.branch === "unavailable" ||
|
|
153
|
+
!plan.expected.kbSnapshotId ||
|
|
154
|
+
plan.expected.kbSnapshotId === "unknown" ||
|
|
155
|
+
plan.expected.kbSnapshotId === "missing" ||
|
|
156
|
+
plan.expected.kbSnapshotId === "unavailable" ||
|
|
157
|
+
!/^[a-f0-9]{64}$/i.test(plan.expected.workspaceSnapshot)) {
|
|
158
|
+
throw new Error("Bootstrap apply failed: ready plans require exact branch, KB snapshot, and workspace snapshot bindings");
|
|
159
|
+
}
|
|
160
|
+
if (!/^[a-f0-9]{64}$/i.test(plan.expected.kbSnapshotId) &&
|
|
161
|
+
!/^empty-source-state-[a-f0-9]{64}$/i.test(plan.expected.kbSnapshotId)) {
|
|
162
|
+
throw new Error("Bootstrap apply failed: ready plans require an exact KB snapshot binding");
|
|
163
|
+
}
|
|
164
|
+
for (const [sourcePath, sourceHash] of Object.entries(plan.expected.sourceHashes)) {
|
|
165
|
+
if (!sourcePath ||
|
|
166
|
+
sourceHash === null ||
|
|
167
|
+
!/^[a-f0-9]{64}$/i.test(sourceHash))
|
|
168
|
+
throw new Error(`Bootstrap apply failed: ready plans require an exact hash for evidence source '${sourcePath}'`);
|
|
169
|
+
}
|
|
170
|
+
if (!Array.isArray(plan.actions) || plan.actions.length === 0)
|
|
171
|
+
throw new Error("Bootstrap apply failed: ready plans must contain actions");
|
|
172
|
+
return orderBootstrapActions(plan.actions);
|
|
173
|
+
}
|
|
174
|
+
function isEntityDeletionApplyArgs(args) {
|
|
175
|
+
return "plan" in args && args.plan.version === "kibi.entity-deletion-plan.v1";
|
|
176
|
+
}
|
|
177
|
+
function validateEntityDeletionPlan(args) {
|
|
178
|
+
if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash) ||
|
|
179
|
+
args.approvedPlanHash !== args.plan.planHash) {
|
|
180
|
+
throw new Error("Entity deletion apply failed: approvedPlanHash does not match planHash");
|
|
181
|
+
}
|
|
182
|
+
const { planHash: _ignored, ...body } = args.plan;
|
|
183
|
+
if (createHash("sha256").update(JSON.stringify(body)).digest("hex") !==
|
|
184
|
+
args.plan.planHash) {
|
|
185
|
+
throw new Error("Entity deletion apply failed: planHash does not match the canonical plan body");
|
|
186
|
+
}
|
|
187
|
+
if (!Array.isArray(args.plan.entityIds) || args.plan.entityIds.length === 0) {
|
|
188
|
+
throw new Error("Entity deletion apply failed: entityIds must be non-empty");
|
|
189
|
+
}
|
|
190
|
+
if (args.plan.supersessionRequired) {
|
|
191
|
+
throw new Error("REQUIREMENT_SUPERSESSION_REQUIRED: authored requirements evolve through a new requirement linked with supersedes; compile and approve that evolution plan instead of deleting the requirement");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function validateMigrationPlanShape(args) {
|
|
195
|
+
if (!isRecord(args.plan))
|
|
196
|
+
throw new Error("Migration apply failed: plan must be an object");
|
|
197
|
+
if (args.plan.version !== "kibi.migration-plan.v2")
|
|
198
|
+
throw new Error("Migration apply failed: unsupported plan version");
|
|
199
|
+
if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash))
|
|
200
|
+
throw new Error("Migration apply failed: approvedPlanHash must be a SHA-256 hash");
|
|
201
|
+
if (args.approvedPlanHash !== args.plan.planHash)
|
|
202
|
+
throw new Error("Migration apply failed: approvedPlanHash does not match plan.planHash");
|
|
203
|
+
const bodyHash = migrationPlanHash({
|
|
204
|
+
version: args.plan.version,
|
|
205
|
+
expected: args.plan.expected,
|
|
206
|
+
scope: args.plan.scope,
|
|
207
|
+
actions: args.plan.actions,
|
|
208
|
+
diagnostics: args.plan.diagnostics,
|
|
209
|
+
});
|
|
210
|
+
if (bodyHash !== args.plan.planHash)
|
|
211
|
+
throw new Error("Migration apply failed: planHash does not match the canonical plan body");
|
|
212
|
+
if (!Array.isArray(args.approvedActionIds) ||
|
|
213
|
+
args.approvedActionIds.length === 0)
|
|
214
|
+
throw new Error("Migration apply failed: approvedActionIds must contain at least one action");
|
|
215
|
+
const selected = new Set(args.approvedActionIds);
|
|
216
|
+
const actions = args.plan.actions.filter((action) => selected.has(action.id));
|
|
217
|
+
if (actions.length !== selected.size)
|
|
218
|
+
throw new Error("Migration apply failed: approvedActionIds contains an action not present in the plan");
|
|
219
|
+
for (const action of actions) {
|
|
220
|
+
if (action.state !== "ready")
|
|
221
|
+
throw new Error(`Migration apply failed: action '${action.id}' is blocked`);
|
|
222
|
+
if (action.safety !== "automatic" || action.autoApplicable !== true)
|
|
223
|
+
throw new Error(`Migration apply failed: action '${action.id}' is not automatic`);
|
|
224
|
+
for (const dependency of action.dependsOn) {
|
|
225
|
+
if (!selected.has(dependency))
|
|
226
|
+
throw new Error(`Migration apply failed: action '${action.id}' requires approved dependency '${dependency}'`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return actions;
|
|
230
|
+
}
|
|
231
|
+
function topologicalActions(actions) {
|
|
232
|
+
const byId = new Map(actions.map((action) => [action.id, action]));
|
|
233
|
+
const result = [];
|
|
234
|
+
const visiting = new Set();
|
|
235
|
+
const visited = new Set();
|
|
236
|
+
const visit = (action) => {
|
|
237
|
+
if (visited.has(action.id))
|
|
238
|
+
return;
|
|
239
|
+
if (visiting.has(action.id))
|
|
240
|
+
throw new Error(`Migration apply failed: action dependency cycle at '${action.id}'`);
|
|
241
|
+
visiting.add(action.id);
|
|
242
|
+
for (const dependency of action.dependsOn) {
|
|
243
|
+
const dependencyAction = byId.get(dependency);
|
|
244
|
+
if (dependencyAction !== undefined)
|
|
245
|
+
visit(dependencyAction);
|
|
246
|
+
}
|
|
247
|
+
visiting.delete(action.id);
|
|
248
|
+
visited.add(action.id);
|
|
249
|
+
result.push(action);
|
|
250
|
+
};
|
|
251
|
+
for (const action of actions)
|
|
252
|
+
visit(action);
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
async function validateSources(context, sourceHashes) {
|
|
256
|
+
let checked = 0;
|
|
257
|
+
for (const [relative, expected] of Object.entries(sourceHashes)) {
|
|
258
|
+
if (!relative ||
|
|
259
|
+
path.isAbsolute(relative) ||
|
|
260
|
+
relative.split(/[\\/]/).includes(".."))
|
|
261
|
+
throw new Error("Apply plan failed: source hash paths must be workspace-relative");
|
|
262
|
+
if (!context.fs)
|
|
263
|
+
throw new Error("Apply plan failed: source hashes require a filesystem-capable runtime");
|
|
264
|
+
let actual;
|
|
265
|
+
try {
|
|
266
|
+
actual = digest(await context.fs.readFile(path.join(context.workspaceRoot, relative)));
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
actual = null;
|
|
270
|
+
}
|
|
271
|
+
if (actual !== expected)
|
|
272
|
+
throw new Error(`Apply plan failed: source hash changed for ${relative}`);
|
|
273
|
+
checked += 1;
|
|
274
|
+
}
|
|
275
|
+
return checked;
|
|
276
|
+
}
|
|
277
|
+
async function applySourceWrites(context, writes, planHash, allowReplay = false) {
|
|
278
|
+
if (writes.length === 0) {
|
|
279
|
+
return { paths: [], rollback: async () => undefined, journalId: null };
|
|
280
|
+
}
|
|
281
|
+
if (!context.fs) {
|
|
282
|
+
throw new Error("Apply plan failed: sourceWrites require a filesystem-capable runtime");
|
|
283
|
+
}
|
|
284
|
+
const fsPort = context.fs;
|
|
285
|
+
const journalPath = path.join(context.workspaceRoot, ".kb", "recovery", `source-writes-${planHash.slice(0, 16)}.json`);
|
|
286
|
+
const journalId = `source-writes-${planHash.slice(0, 16)}`;
|
|
287
|
+
const sameEntries = (entries) => entries.length === writes.length &&
|
|
288
|
+
entries.every((entry) => writes.some((write) => write.path === entry.path &&
|
|
289
|
+
(write.mode ?? "write") === entry.mode &&
|
|
290
|
+
write.beforeHash === entry.beforeHash &&
|
|
291
|
+
write.afterHash === entry.afterHash));
|
|
292
|
+
const readJournal = async () => {
|
|
293
|
+
try {
|
|
294
|
+
const parsed = JSON.parse(await fsPort.readFile(journalPath));
|
|
295
|
+
if (parsed.version === 1 &&
|
|
296
|
+
parsed.planHash === planHash &&
|
|
297
|
+
Array.isArray(parsed.entries) &&
|
|
298
|
+
(parsed.state === "prepared" ||
|
|
299
|
+
parsed.state === "publishing_sources" ||
|
|
300
|
+
parsed.state === "sources_committed" ||
|
|
301
|
+
parsed.state === "compiled_published" ||
|
|
302
|
+
parsed.state === "committed" ||
|
|
303
|
+
parsed.state === "repair_required" ||
|
|
304
|
+
parsed.state === "rolled_back") &&
|
|
305
|
+
parsed.entries.every((entry) => entry && typeof entry === "object") &&
|
|
306
|
+
sameEntries(parsed.entries)) {
|
|
307
|
+
return parsed;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// First attempt or an incomplete journal.
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
};
|
|
315
|
+
const prior = await readJournal();
|
|
316
|
+
const priorPaths = prior?.entries.map((entry) => entry.path) ?? [];
|
|
317
|
+
if (prior &&
|
|
318
|
+
[
|
|
319
|
+
"committed",
|
|
320
|
+
"sources_committed",
|
|
321
|
+
"compiled_published",
|
|
322
|
+
"repair_required",
|
|
323
|
+
].includes(prior.state)) {
|
|
324
|
+
let allAfter = true;
|
|
325
|
+
for (const entry of prior.entries) {
|
|
326
|
+
try {
|
|
327
|
+
const current = await context.fs.readFile(path.resolve(context.workspaceRoot, entry.path));
|
|
328
|
+
if (entry.afterHash === null || digest(current) !== entry.afterHash) {
|
|
329
|
+
allAfter = false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
if (entry.afterHash !== null)
|
|
334
|
+
allAfter = false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (allAfter) {
|
|
338
|
+
if (!allowReplay) {
|
|
339
|
+
throw new Error(`MUTATION_ALREADY_COMMITTED: source plan ${planHash} already crossed the authoritative commit boundary; use kb_apply_plan recoveryJournalId=${journalId} instead of retrying the original mutation`);
|
|
340
|
+
}
|
|
341
|
+
for (const entry of prior.entries) {
|
|
342
|
+
if (entry.mode === "write" && entry.afterHash !== null) {
|
|
343
|
+
writePendingSourceReceipt(context.workspaceRoot, entry.path, entry.afterHash);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return { paths: priorPaths, rollback: async () => undefined, journalId };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (prior &&
|
|
350
|
+
(prior.state === "prepared" || prior.state === "publishing_sources")) {
|
|
351
|
+
// A crash before the authoritative source commit must restore every
|
|
352
|
+
// before-image. Refuse recovery if another writer changed a target to a
|
|
353
|
+
// hash that is neither the planned before nor after value.
|
|
354
|
+
for (const entry of prior.entries) {
|
|
355
|
+
const absolute = path.resolve(context.workspaceRoot, entry.path);
|
|
356
|
+
let current;
|
|
357
|
+
try {
|
|
358
|
+
current = await context.fs.readFile(absolute);
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
current = undefined;
|
|
362
|
+
}
|
|
363
|
+
const currentHash = current === undefined ? null : digest(current);
|
|
364
|
+
if (currentHash !== entry.beforeHash && currentHash !== entry.afterHash) {
|
|
365
|
+
throw new Error(`Apply plan recovery refused: ${entry.path} changed outside its journal`);
|
|
366
|
+
}
|
|
367
|
+
if (entry.beforeExisted) {
|
|
368
|
+
const before = await context.fs.readFile(entry.beforeStage);
|
|
369
|
+
await context.fs.writeFile(absolute, before);
|
|
370
|
+
}
|
|
371
|
+
else if (context.fs.unlink) {
|
|
372
|
+
await context.fs.unlink(absolute);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
await context.fs.writeFile(journalPath, `${JSON.stringify({ ...prior, state: "rolled_back" }, null, 2)}\n`);
|
|
376
|
+
return { paths: priorPaths, rollback: async () => undefined, journalId };
|
|
377
|
+
}
|
|
378
|
+
const originals = [];
|
|
379
|
+
const entries = [];
|
|
380
|
+
const paths = [];
|
|
381
|
+
try {
|
|
382
|
+
// Validate every target and hash before touching the working tree.
|
|
383
|
+
for (const write of writes) {
|
|
384
|
+
if (!write.path ||
|
|
385
|
+
path.isAbsolute(write.path) ||
|
|
386
|
+
write.path.split(/[\\/]/).includes("..")) {
|
|
387
|
+
throw new Error(`Apply plan failed: sourceWrites.path must be workspace-relative: ${write.path}`);
|
|
388
|
+
}
|
|
389
|
+
const absolute = path.resolve(context.workspaceRoot, write.path);
|
|
390
|
+
const root = path.resolve(context.workspaceRoot);
|
|
391
|
+
if (absolute !== root && !absolute.startsWith(`${root}${path.sep}`)) {
|
|
392
|
+
throw new Error(`Apply plan failed: sourceWrites.path escapes workspace: ${write.path}`);
|
|
393
|
+
}
|
|
394
|
+
const workspaceRelative = path
|
|
395
|
+
.relative(root, absolute)
|
|
396
|
+
.split(path.sep)
|
|
397
|
+
.join("/");
|
|
398
|
+
if (workspaceRelative === ".kb" || isDerivedKbPath(workspaceRelative)) {
|
|
399
|
+
throw new Error("Apply plan failed: sourceWrites.path cannot target Kibi's derived .kb runtime trees");
|
|
400
|
+
}
|
|
401
|
+
let existingPath = absolute;
|
|
402
|
+
while (!existsSync(existingPath) &&
|
|
403
|
+
path.dirname(existingPath) !== existingPath) {
|
|
404
|
+
existingPath = path.dirname(existingPath);
|
|
405
|
+
}
|
|
406
|
+
const realExisting = realpathSync.native(existingPath);
|
|
407
|
+
if (realExisting !== root &&
|
|
408
|
+
!realExisting.startsWith(`${root}${path.sep}`)) {
|
|
409
|
+
throw new Error(`Apply plan failed: sourceWrites.path follows a symlink outside the workspace: ${write.path}`);
|
|
410
|
+
}
|
|
411
|
+
const existing = await context.fs
|
|
412
|
+
.readFile(absolute)
|
|
413
|
+
.catch(() => undefined);
|
|
414
|
+
const beforeHash = existing === undefined ? null : digest(existing);
|
|
415
|
+
if (beforeHash !== write.beforeHash) {
|
|
416
|
+
throw new Error(`Apply plan failed: source hash changed for ${write.path}`);
|
|
417
|
+
}
|
|
418
|
+
const mode = write.mode ?? "write";
|
|
419
|
+
if (mode === "write" &&
|
|
420
|
+
(write.body === undefined ||
|
|
421
|
+
write.afterHash === null ||
|
|
422
|
+
digest(write.body) !== write.afterHash)) {
|
|
423
|
+
throw new Error(`Apply plan failed: afterHash does not match staged body for ${write.path}`);
|
|
424
|
+
}
|
|
425
|
+
if (mode === "delete" && write.afterHash !== null) {
|
|
426
|
+
throw new Error(`Apply plan failed: delete source write must have a null afterHash for ${write.path}`);
|
|
427
|
+
}
|
|
428
|
+
originals.push({ absolute, body: existing });
|
|
429
|
+
paths.push(write.path);
|
|
430
|
+
const stageBase = path.join(context.workspaceRoot, ".kb", "recovery", `${journalId}-${entries.length}`);
|
|
431
|
+
entries.push({
|
|
432
|
+
path: write.path,
|
|
433
|
+
mode,
|
|
434
|
+
beforeHash: write.beforeHash,
|
|
435
|
+
afterHash: write.afterHash,
|
|
436
|
+
beforeExisted: existing !== undefined,
|
|
437
|
+
beforeStage: `${stageBase}.before`,
|
|
438
|
+
afterStage: `${stageBase}.after`,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
// Stage both versions and publish a prepared journal before any
|
|
442
|
+
// authoritative working-tree write. This makes a crash replayable.
|
|
443
|
+
await context.fs.mkdir(path.dirname(journalPath));
|
|
444
|
+
for (let index = 0; index < writes.length; index += 1) {
|
|
445
|
+
const original = originals[index];
|
|
446
|
+
const entry = entries[index];
|
|
447
|
+
const write = writes[index];
|
|
448
|
+
if (!original || !entry || !write)
|
|
449
|
+
continue;
|
|
450
|
+
if (original.body !== undefined) {
|
|
451
|
+
await context.fs.writeFile(entry.beforeStage, original.body);
|
|
452
|
+
}
|
|
453
|
+
if ((write.mode ?? "write") === "write") {
|
|
454
|
+
await context.fs.writeFile(entry.afterStage, write.body ?? "");
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
await context.fs.writeFile(journalPath, `${JSON.stringify({ version: 1, planHash, state: "prepared", entries }, null, 2)}\n`);
|
|
458
|
+
// Publish all target files. Journal each boundary so a crash can be
|
|
459
|
+
// rolled back without trusting in-memory originals.
|
|
460
|
+
for (let index = 0; index < writes.length; index += 1) {
|
|
461
|
+
const absolute = originals[index]?.absolute;
|
|
462
|
+
const write = writes[index];
|
|
463
|
+
if (!absolute || !write)
|
|
464
|
+
continue;
|
|
465
|
+
await context.fs.mkdir(path.dirname(absolute));
|
|
466
|
+
await context.fs.writeFile(journalPath, `${JSON.stringify({ version: 1, planHash, state: "publishing_sources", entries }, null, 2)}\n`);
|
|
467
|
+
if ((write.mode ?? "write") === "delete") {
|
|
468
|
+
if (!context.fs.unlink) {
|
|
469
|
+
throw new Error(`Apply plan failed: delete requires filesystem unlink support: ${write.path}`);
|
|
470
|
+
}
|
|
471
|
+
await context.fs.unlink(absolute);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
const staged = `${absolute}.kibi-stage-${journalId}-${index}`;
|
|
475
|
+
await context.fs.writeFile(staged, write.body ?? "");
|
|
476
|
+
if (context.fs.rename) {
|
|
477
|
+
await context.fs.rename(staged, absolute);
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
// Test and constrained host ports may not expose rename. Keep the
|
|
481
|
+
// compatibility fallback explicit; production nodeFilesystem uses
|
|
482
|
+
// same-directory rename for atomic replacement.
|
|
483
|
+
await context.fs.writeFile(absolute, write.body ?? "");
|
|
484
|
+
if (context.fs.unlink)
|
|
485
|
+
await context.fs.unlink(staged).catch(() => undefined);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
await context.fs.writeFile(journalPath, `${JSON.stringify({ version: 1, planHash, state: "sources_committed", entries }, null, 2)}\n`);
|
|
490
|
+
// A newly authored file is intentionally excluded from ordinary Git
|
|
491
|
+
// discovery until the operator stages it. The receipt binds that pending
|
|
492
|
+
// input to the exact bytes committed by this plan.
|
|
493
|
+
for (const write of writes) {
|
|
494
|
+
if ((write.mode ?? "write") === "write" && write.afterHash !== null) {
|
|
495
|
+
writePendingSourceReceipt(context.workspaceRoot, write.path, write.afterHash);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
for (const original of [...originals].reverse()) {
|
|
501
|
+
try {
|
|
502
|
+
if (original.body === undefined && context.fs.unlink) {
|
|
503
|
+
await context.fs.unlink(original.absolute);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
await context.fs.writeFile(original.absolute, original.body ?? "");
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
// Preserve the original failure; the journal remains available for
|
|
511
|
+
// the next recovery attempt.
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
await context.fs.mkdir(path.dirname(journalPath));
|
|
516
|
+
await context.fs.writeFile(journalPath, `${JSON.stringify({ version: 1, planHash, state: "rolled_back", entries }, null, 2)}\n`);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
// Best effort only; the original error is authoritative.
|
|
520
|
+
}
|
|
521
|
+
throw error;
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
paths,
|
|
525
|
+
journalId,
|
|
526
|
+
// Source publication is the authoritative commit boundary. Derived
|
|
527
|
+
// compiled effects must be repaired from the journal, never rolled back
|
|
528
|
+
// by retrying the original mutation.
|
|
529
|
+
rollback: async () => undefined,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
async function markSourceJournal(context, journalId, state) {
|
|
533
|
+
if (!journalId || !context.fs)
|
|
534
|
+
return;
|
|
535
|
+
const journalPath = path.join(context.workspaceRoot, ".kb", "recovery", `${journalId}.json`);
|
|
536
|
+
try {
|
|
537
|
+
const current = JSON.parse(await context.fs.readFile(journalPath));
|
|
538
|
+
await context.fs.writeFile(journalPath, `${JSON.stringify({ ...current, state }, null, 2)}\n`);
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
// Journal repair is surfaced by the next status/check; do not hide the
|
|
542
|
+
// authoritative operation result behind a best-effort metadata write.
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
async function executeSourceRecovery(args, context) {
|
|
546
|
+
if (!context.fs)
|
|
547
|
+
throw new Error("Source recovery requires a filesystem-capable runtime");
|
|
548
|
+
if (!/^[A-Za-z0-9._-]+$/.test(args.recoveryJournalId)) {
|
|
549
|
+
throw new Error("Source recovery journal ID is invalid");
|
|
550
|
+
}
|
|
551
|
+
const journalPath = path.join(context.workspaceRoot, ".kb", "recovery", `${args.recoveryJournalId}.json`);
|
|
552
|
+
const journal = JSON.parse(await context.fs.readFile(journalPath));
|
|
553
|
+
if (journal.version !== 1 ||
|
|
554
|
+
typeof journal.planHash !== "string" ||
|
|
555
|
+
!Array.isArray(journal.entries) ||
|
|
556
|
+
!["sources_committed", "compiled_published", "repair_required"].includes(journal.state ?? "")) {
|
|
557
|
+
throw new Error("Source recovery requires a committed or repair_required journal");
|
|
558
|
+
}
|
|
559
|
+
const writes = [];
|
|
560
|
+
for (const entry of journal.entries) {
|
|
561
|
+
const body = entry.mode === "write"
|
|
562
|
+
? await context.fs.readFile(entry.afterStage)
|
|
563
|
+
: undefined;
|
|
564
|
+
writes.push({
|
|
565
|
+
path: entry.path,
|
|
566
|
+
mode: entry.mode,
|
|
567
|
+
beforeHash: entry.beforeHash,
|
|
568
|
+
afterHash: entry.afterHash,
|
|
569
|
+
...(body === undefined ? {} : { body }),
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
const sourceWrites = await applySourceWrites(context, writes, journal.planHash, true);
|
|
573
|
+
for (const write of writes) {
|
|
574
|
+
if (write.mode === "write" && write.afterHash !== null) {
|
|
575
|
+
writePendingSourceReceipt(context.workspaceRoot, write.path, write.afterHash);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const sync = await syncCommand({
|
|
579
|
+
workspaceRoot: context.workspaceRoot,
|
|
580
|
+
rebuild: true,
|
|
581
|
+
});
|
|
582
|
+
await markSourceJournal(context, sourceWrites.journalId, "committed");
|
|
583
|
+
return {
|
|
584
|
+
content: [
|
|
585
|
+
{
|
|
586
|
+
type: "text",
|
|
587
|
+
text: `Repaired source journal ${args.recoveryJournalId}.`,
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
structuredContent: {
|
|
591
|
+
version: PLAN_APPLY_RESULT_VERSION,
|
|
592
|
+
outcome: "replayed",
|
|
593
|
+
planHash: journal.planHash,
|
|
594
|
+
changedEntities: sync.entityCounts
|
|
595
|
+
? Object.values(sync.entityCounts).reduce((sum, count) => sum + count, 0)
|
|
596
|
+
: 0,
|
|
597
|
+
changedRelationships: sync.relationshipCount ?? 0,
|
|
598
|
+
changedPaths: sourceWrites.paths,
|
|
599
|
+
finalSnapshots: {
|
|
600
|
+
branch: sync.branch,
|
|
601
|
+
kbSnapshotId: "recovered",
|
|
602
|
+
workspaceSnapshot: "recovered",
|
|
603
|
+
},
|
|
604
|
+
validationSummary: {
|
|
605
|
+
stepsValidated: 0,
|
|
606
|
+
stepsApplied: 0,
|
|
607
|
+
sourceHashesChecked: writes.length,
|
|
608
|
+
notes: [
|
|
609
|
+
"Compiled state rebuilt from the authoritative recovery journal.",
|
|
610
|
+
],
|
|
611
|
+
},
|
|
612
|
+
recoveryJournalId: args.recoveryJournalId,
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
async function executeBootstrapPlan(args, context, recovery = false, remainingActions, priorResults = []) {
|
|
617
|
+
const actions = recovery
|
|
618
|
+
? [...(remainingActions ?? args.plan.actions)]
|
|
619
|
+
: validateBootstrapPlanShape(args);
|
|
620
|
+
const prolog = context.prolog ?? (await context.ensureProlog?.());
|
|
621
|
+
if (!prolog)
|
|
622
|
+
throw new Error("Bootstrap apply requires a Prolog runtime");
|
|
623
|
+
const operationContext = { ...context, prolog, sourceFirst: true };
|
|
624
|
+
const statusResult = await executeStatus({}, operationContext);
|
|
625
|
+
const status = statusResult.structuredContent;
|
|
626
|
+
if (!status)
|
|
627
|
+
throw new Error("Bootstrap apply failed: status returned no payload");
|
|
628
|
+
const workspace = await readWorkspaceSnapshot(operationContext);
|
|
629
|
+
const boundLiveKbSnapshot = status.snapshotId === "missing" &&
|
|
630
|
+
workspace.available &&
|
|
631
|
+
/^[a-f0-9]{64}$/i.test(workspace.snapshot.hash)
|
|
632
|
+
? bootstrapEmptyKbSnapshotId({
|
|
633
|
+
branch: status.branch,
|
|
634
|
+
workspaceSnapshot: workspace.snapshot.hash,
|
|
635
|
+
sourceHashes: args.plan.expected.sourceHashes,
|
|
636
|
+
})
|
|
637
|
+
: status.snapshotId;
|
|
638
|
+
if (!recovery) {
|
|
639
|
+
if (args.plan.expected.branch !== "unknown" &&
|
|
640
|
+
status.branch !== args.plan.expected.branch)
|
|
641
|
+
throw new Error("Bootstrap apply failed: branch changed since planning");
|
|
642
|
+
if (boundLiveKbSnapshot !== args.plan.expected.kbSnapshotId)
|
|
643
|
+
throw new Error("Bootstrap apply failed: KB snapshot changed since planning");
|
|
644
|
+
if (args.plan.expected.workspaceSnapshot !== "unknown" &&
|
|
645
|
+
(!workspace.available ||
|
|
646
|
+
workspace.snapshot.hash !== args.plan.expected.workspaceSnapshot))
|
|
647
|
+
throw new Error("Bootstrap apply failed: workspace snapshot changed since planning");
|
|
648
|
+
await validateSources(operationContext, args.plan.expected.sourceHashes);
|
|
649
|
+
}
|
|
650
|
+
const journalId = `bootstrap-${args.plan.planHash.slice(0, 16)}`;
|
|
651
|
+
const journalPath = path.join(context.workspaceRoot, ".kb", "recovery", `${journalId}.json`);
|
|
652
|
+
if (!recovery && context.fs) {
|
|
653
|
+
try {
|
|
654
|
+
await context.fs.readFile(journalPath);
|
|
655
|
+
throw new Error(`Bootstrap apply refused: journal ${journalId} already exists; recover it with kb_apply_plan recoveryJournalId=${journalId} instead of replaying the original plan`);
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
if (error instanceof Error && error.message.includes("journal "))
|
|
659
|
+
throw error;
|
|
660
|
+
// No journal exists yet; this is the first application attempt.
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const results = [...priorResults];
|
|
664
|
+
let changedEntities = 0;
|
|
665
|
+
let changedRelationships = 0;
|
|
666
|
+
const failures = [];
|
|
667
|
+
let activeActionId;
|
|
668
|
+
const checkpoint = async () => {
|
|
669
|
+
const current = (await executeStatus({}, operationContext))
|
|
670
|
+
.structuredContent;
|
|
671
|
+
const currentWorkspace = await readWorkspaceSnapshot(operationContext);
|
|
672
|
+
return {
|
|
673
|
+
branch: current?.branch ?? "unavailable",
|
|
674
|
+
kbSnapshotId: current?.snapshotId === "missing" &&
|
|
675
|
+
currentWorkspace.available &&
|
|
676
|
+
/^[a-f0-9]{64}$/i.test(currentWorkspace.snapshot.hash)
|
|
677
|
+
? bootstrapEmptyKbSnapshotId({
|
|
678
|
+
branch: current?.branch ?? "unavailable",
|
|
679
|
+
workspaceSnapshot: currentWorkspace.snapshot.hash,
|
|
680
|
+
sourceHashes: args.plan.expected.sourceHashes,
|
|
681
|
+
})
|
|
682
|
+
: (current?.snapshotId ?? "unavailable"),
|
|
683
|
+
workspaceSnapshot: currentWorkspace.available
|
|
684
|
+
? currentWorkspace.snapshot.hash
|
|
685
|
+
: "unavailable",
|
|
686
|
+
};
|
|
687
|
+
};
|
|
688
|
+
const initialCheckpoint = await checkpoint();
|
|
689
|
+
let lastCheckpoint = initialCheckpoint;
|
|
690
|
+
const writeJournal = async (state, activeActionId, currentCheckpoint = lastCheckpoint) => {
|
|
691
|
+
if (!context.fs)
|
|
692
|
+
return;
|
|
693
|
+
await context.fs.mkdir(path.dirname(journalPath));
|
|
694
|
+
await context.fs.writeFile(journalPath, `${JSON.stringify({ version: 2, kind: "bootstrap", plan: args.plan, state, checkpoint: currentCheckpoint, ...(activeActionId ? { activeActionId } : {}), results }, null, 2)}\n`);
|
|
695
|
+
};
|
|
696
|
+
await writeJournal("applying", undefined, initialCheckpoint);
|
|
697
|
+
try {
|
|
698
|
+
for (const action of actions) {
|
|
699
|
+
try {
|
|
700
|
+
activeActionId = action.id;
|
|
701
|
+
await writeJournal("applying", activeActionId);
|
|
702
|
+
const result = await executeUpsert(asUpsert(action.payload), operationContext);
|
|
703
|
+
const payload = result.structuredContent;
|
|
704
|
+
if (payload && typeof payload === "object") {
|
|
705
|
+
const row = payload;
|
|
706
|
+
changedEntities +=
|
|
707
|
+
Number(row.created ?? 0) + Number(row.updated ?? 0);
|
|
708
|
+
changedRelationships += Number(row.relationships_created ?? 0);
|
|
709
|
+
// A source-first upsert can commit its authoritative mutation while
|
|
710
|
+
// a derived effect still needs repair. Treat that as a committed
|
|
711
|
+
// action with repair state, checkpoint it, and stop the bootstrap
|
|
712
|
+
// graph. Recovery will see the applied action in the journal and
|
|
713
|
+
// resume only its remaining dependants.
|
|
714
|
+
if (row.status === "committed_with_repairs") {
|
|
715
|
+
results.push({
|
|
716
|
+
actionId: action.id,
|
|
717
|
+
outcome: "applied",
|
|
718
|
+
detail: "Applied with committed derived effects requiring repair.",
|
|
719
|
+
});
|
|
720
|
+
const effectFailures = Array.isArray(row.effectFailures)
|
|
721
|
+
? row.effectFailures
|
|
722
|
+
.filter((failure) => failure !== null &&
|
|
723
|
+
typeof failure === "object" &&
|
|
724
|
+
!Array.isArray(failure))
|
|
725
|
+
.map((failure) => ({ ...failure, actionId: action.id }))
|
|
726
|
+
: [];
|
|
727
|
+
failures.push(...effectFailures);
|
|
728
|
+
const nextActions = Array.isArray(row.nextActions)
|
|
729
|
+
? row.nextActions.filter((nextAction) => nextAction !== null &&
|
|
730
|
+
typeof nextAction === "object" &&
|
|
731
|
+
!Array.isArray(nextAction))
|
|
732
|
+
: [];
|
|
733
|
+
lastCheckpoint = await checkpoint();
|
|
734
|
+
activeActionId = undefined;
|
|
735
|
+
await writeJournal("repair_required", action.id, lastCheckpoint);
|
|
736
|
+
return {
|
|
737
|
+
content: [
|
|
738
|
+
{
|
|
739
|
+
type: "text",
|
|
740
|
+
text: `Bootstrap plan ${args.plan.planHash.slice(0, 12)} committed an action with repair effects; repair journal ${journalId}.`,
|
|
741
|
+
},
|
|
742
|
+
],
|
|
743
|
+
structuredContent: {
|
|
744
|
+
version: PLAN_APPLY_RESULT_VERSION,
|
|
745
|
+
outcome: "partially_applied",
|
|
746
|
+
planHash: args.plan.planHash,
|
|
747
|
+
actionResults: results,
|
|
748
|
+
changedEntities,
|
|
749
|
+
changedRelationships,
|
|
750
|
+
finalSnapshots: {
|
|
751
|
+
branch: lastCheckpoint.branch,
|
|
752
|
+
kbSnapshotId: lastCheckpoint.kbSnapshotId,
|
|
753
|
+
workspaceSnapshot: lastCheckpoint.workspaceSnapshot,
|
|
754
|
+
},
|
|
755
|
+
recoveryJournalId: journalId,
|
|
756
|
+
changedPaths: [],
|
|
757
|
+
validationSummary: {
|
|
758
|
+
stepsValidated: actions.length,
|
|
759
|
+
stepsApplied: results.filter((row) => row.outcome === "applied").length,
|
|
760
|
+
sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes).length,
|
|
761
|
+
notes: [
|
|
762
|
+
"Bootstrap stopped after an authoritative action committed with derived repair effects.",
|
|
763
|
+
],
|
|
764
|
+
},
|
|
765
|
+
status: "committed_with_repairs",
|
|
766
|
+
effectFailures: failures,
|
|
767
|
+
nextActions: [
|
|
768
|
+
...nextActions,
|
|
769
|
+
{
|
|
770
|
+
operation: "kb_apply_plan",
|
|
771
|
+
input: { recoveryJournalId: journalId },
|
|
772
|
+
reason: "Resume the remaining bootstrap actions from the immutable recovery journal; do not retry the original plan.",
|
|
773
|
+
required: true,
|
|
774
|
+
},
|
|
775
|
+
],
|
|
776
|
+
},
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
results.push({
|
|
781
|
+
actionId: action.id,
|
|
782
|
+
outcome: "applied",
|
|
783
|
+
detail: "Applied sequentially.",
|
|
784
|
+
});
|
|
785
|
+
lastCheckpoint = await checkpoint();
|
|
786
|
+
activeActionId = undefined;
|
|
787
|
+
await writeJournal("applying", undefined, lastCheckpoint);
|
|
788
|
+
}
|
|
789
|
+
catch (error) {
|
|
790
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
791
|
+
results.push({ actionId: action.id, outcome: "failed", detail });
|
|
792
|
+
failures.push({ actionId: action.id, detail });
|
|
793
|
+
await writeJournal("repair_required", activeActionId, lastCheckpoint);
|
|
794
|
+
return {
|
|
795
|
+
content: [
|
|
796
|
+
{
|
|
797
|
+
type: "text",
|
|
798
|
+
text: `Bootstrap plan ${args.plan.planHash.slice(0, 12)} partially applied; repair journal ${journalId}.`,
|
|
799
|
+
},
|
|
800
|
+
],
|
|
801
|
+
structuredContent: {
|
|
802
|
+
version: PLAN_APPLY_RESULT_VERSION,
|
|
803
|
+
outcome: "partially_applied",
|
|
804
|
+
planHash: args.plan.planHash,
|
|
805
|
+
actionResults: results,
|
|
806
|
+
changedEntities,
|
|
807
|
+
changedRelationships,
|
|
808
|
+
finalSnapshots: {
|
|
809
|
+
branch: lastCheckpoint.branch,
|
|
810
|
+
kbSnapshotId: lastCheckpoint.kbSnapshotId,
|
|
811
|
+
workspaceSnapshot: lastCheckpoint.workspaceSnapshot,
|
|
812
|
+
},
|
|
813
|
+
recoveryJournalId: journalId,
|
|
814
|
+
changedPaths: [],
|
|
815
|
+
validationSummary: {
|
|
816
|
+
stepsValidated: actions.length,
|
|
817
|
+
stepsApplied: results.filter((row) => row.outcome === "applied")
|
|
818
|
+
.length,
|
|
819
|
+
sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes)
|
|
820
|
+
.length,
|
|
821
|
+
notes: [
|
|
822
|
+
"Bootstrap application stopped at a repairable action failure.",
|
|
823
|
+
],
|
|
824
|
+
},
|
|
825
|
+
status: "committed_with_repairs",
|
|
826
|
+
effectFailures: failures,
|
|
827
|
+
nextActions: [
|
|
828
|
+
{
|
|
829
|
+
operation: "kb_apply_plan",
|
|
830
|
+
input: { recoveryJournalId: journalId },
|
|
831
|
+
reason: "Resume the remaining bootstrap actions from the immutable recovery journal; do not retry the original plan.",
|
|
832
|
+
required: true,
|
|
833
|
+
},
|
|
834
|
+
],
|
|
835
|
+
},
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
842
|
+
failures.push({ kind: "bootstrap", detail });
|
|
843
|
+
await writeJournal("repair_required", activeActionId, lastCheckpoint);
|
|
844
|
+
return {
|
|
845
|
+
content: [
|
|
846
|
+
{
|
|
847
|
+
type: "text",
|
|
848
|
+
text: `Bootstrap plan ${args.plan.planHash.slice(0, 12)} requires repair; journal ${journalId}.`,
|
|
849
|
+
},
|
|
850
|
+
],
|
|
851
|
+
structuredContent: {
|
|
852
|
+
version: PLAN_APPLY_RESULT_VERSION,
|
|
853
|
+
outcome: "partially_applied",
|
|
854
|
+
planHash: args.plan.planHash,
|
|
855
|
+
actionResults: results,
|
|
856
|
+
changedEntities,
|
|
857
|
+
changedRelationships,
|
|
858
|
+
finalSnapshots: {
|
|
859
|
+
branch: lastCheckpoint.branch,
|
|
860
|
+
kbSnapshotId: lastCheckpoint.kbSnapshotId,
|
|
861
|
+
workspaceSnapshot: lastCheckpoint.workspaceSnapshot,
|
|
862
|
+
},
|
|
863
|
+
recoveryJournalId: journalId,
|
|
864
|
+
changedPaths: [],
|
|
865
|
+
validationSummary: {
|
|
866
|
+
stepsValidated: actions.length,
|
|
867
|
+
stepsApplied: results.filter((row) => row.outcome === "applied")
|
|
868
|
+
.length,
|
|
869
|
+
sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes)
|
|
870
|
+
.length,
|
|
871
|
+
notes: [
|
|
872
|
+
"Bootstrap application stopped before its full action graph completed.",
|
|
873
|
+
],
|
|
874
|
+
},
|
|
875
|
+
status: "committed_with_repairs",
|
|
876
|
+
effectFailures: failures,
|
|
877
|
+
nextActions: [
|
|
878
|
+
{
|
|
879
|
+
operation: "kb_apply_plan",
|
|
880
|
+
input: { recoveryJournalId: journalId },
|
|
881
|
+
reason: "Resume the remaining bootstrap actions from the immutable recovery journal; do not retry the original plan.",
|
|
882
|
+
required: true,
|
|
883
|
+
},
|
|
884
|
+
],
|
|
885
|
+
},
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
await writeJournal("committed");
|
|
889
|
+
const finalStatus = (await executeStatus({}, operationContext)).structuredContent ?? status;
|
|
890
|
+
const finalWorkspace = await readWorkspaceSnapshot(operationContext);
|
|
891
|
+
return {
|
|
892
|
+
content: [
|
|
893
|
+
{
|
|
894
|
+
type: "text",
|
|
895
|
+
text: `Applied bootstrap plan ${args.plan.planHash.slice(0, 12)}.`,
|
|
896
|
+
},
|
|
897
|
+
],
|
|
898
|
+
structuredContent: {
|
|
899
|
+
version: PLAN_APPLY_RESULT_VERSION,
|
|
900
|
+
outcome: "applied",
|
|
901
|
+
planHash: args.plan.planHash,
|
|
902
|
+
actionResults: results,
|
|
903
|
+
changedEntities,
|
|
904
|
+
changedRelationships,
|
|
905
|
+
finalSnapshots: {
|
|
906
|
+
branch: finalStatus.branch,
|
|
907
|
+
kbSnapshotId: finalStatus.snapshotId,
|
|
908
|
+
workspaceSnapshot: finalWorkspace.available
|
|
909
|
+
? finalWorkspace.snapshot.hash
|
|
910
|
+
: "unknown",
|
|
911
|
+
},
|
|
912
|
+
recoveryJournalId: context.fs ? journalId : null,
|
|
913
|
+
changedPaths: [],
|
|
914
|
+
validationSummary: {
|
|
915
|
+
stepsValidated: actions.length,
|
|
916
|
+
stepsApplied: results.filter((row) => row.outcome === "applied").length,
|
|
917
|
+
sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes)
|
|
918
|
+
.length,
|
|
919
|
+
notes: [
|
|
920
|
+
"Bootstrap actions applied sequentially through the shared plan executor.",
|
|
921
|
+
],
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
// implements REQ-kibi-change-to-proof-plan-compiler, REQ-agent-guided-migration-orchestration
|
|
927
|
+
export async function executeApplyPlan(args, context) {
|
|
928
|
+
if ("recoveryJournalId" in args) {
|
|
929
|
+
if (args.recoveryJournalId.startsWith("bootstrap-")) {
|
|
930
|
+
if (!context.fs)
|
|
931
|
+
throw new Error("Bootstrap recovery requires a filesystem-capable runtime");
|
|
932
|
+
if (!/^bootstrap-[a-f0-9]{16}$/.test(args.recoveryJournalId))
|
|
933
|
+
throw new Error("Bootstrap recovery journal ID is invalid");
|
|
934
|
+
const journalPath = path.join(context.workspaceRoot, ".kb", "recovery", `${args.recoveryJournalId}.json`);
|
|
935
|
+
const journal = JSON.parse(await context.fs.readFile(journalPath));
|
|
936
|
+
if (journal.version !== 2 ||
|
|
937
|
+
journal.kind !== "bootstrap" ||
|
|
938
|
+
!journal.plan)
|
|
939
|
+
throw new Error("Bootstrap recovery journal is invalid");
|
|
940
|
+
if (!/^[a-f0-9]{64}$/i.test(journal.plan.planHash) ||
|
|
941
|
+
bootstrapPlanHash(journal.plan) !== journal.plan.planHash)
|
|
942
|
+
throw new Error("Bootstrap recovery journal plan hash is invalid");
|
|
943
|
+
const ordered = validateBootstrapPlanShape({
|
|
944
|
+
plan: journal.plan,
|
|
945
|
+
approvedPlanHash: journal.plan.planHash,
|
|
946
|
+
});
|
|
947
|
+
if (!journal.checkpoint?.branch ||
|
|
948
|
+
!journal.checkpoint.kbSnapshotId ||
|
|
949
|
+
!journal.checkpoint.workspaceSnapshot)
|
|
950
|
+
throw new Error("Bootstrap recovery journal has no state checkpoint");
|
|
951
|
+
const recoveryStatus = (await executeStatus({}, context))
|
|
952
|
+
.structuredContent;
|
|
953
|
+
const recoveryWorkspace = await readWorkspaceSnapshot(context);
|
|
954
|
+
const liveCheckpoint = {
|
|
955
|
+
branch: recoveryStatus?.branch ?? "unavailable",
|
|
956
|
+
kbSnapshotId: recoveryStatus?.snapshotId === "missing" &&
|
|
957
|
+
recoveryWorkspace.available &&
|
|
958
|
+
/^[a-f0-9]{64}$/i.test(recoveryWorkspace.snapshot.hash)
|
|
959
|
+
? bootstrapEmptyKbSnapshotId({
|
|
960
|
+
branch: recoveryStatus?.branch ?? "unavailable",
|
|
961
|
+
workspaceSnapshot: recoveryWorkspace.snapshot.hash,
|
|
962
|
+
sourceHashes: journal.plan.expected.sourceHashes,
|
|
963
|
+
})
|
|
964
|
+
: (recoveryStatus?.snapshotId ?? "unavailable"),
|
|
965
|
+
workspaceSnapshot: recoveryWorkspace.available
|
|
966
|
+
? recoveryWorkspace.snapshot.hash
|
|
967
|
+
: "unavailable",
|
|
968
|
+
};
|
|
969
|
+
if (liveCheckpoint.branch !== journal.checkpoint.branch ||
|
|
970
|
+
liveCheckpoint.kbSnapshotId !== journal.checkpoint.kbSnapshotId ||
|
|
971
|
+
liveCheckpoint.workspaceSnapshot !==
|
|
972
|
+
journal.checkpoint.workspaceSnapshot)
|
|
973
|
+
throw new Error("Bootstrap recovery refused: repository state changed since the last action checkpoint");
|
|
974
|
+
const actionIds = new Set(ordered.map((action) => action.id));
|
|
975
|
+
const applied = new Set();
|
|
976
|
+
for (const row of journal.results ?? []) {
|
|
977
|
+
if (typeof row.actionId !== "string" || !actionIds.has(row.actionId))
|
|
978
|
+
throw new Error("Bootstrap recovery journal contains an unknown action");
|
|
979
|
+
if (row.outcome === "applied")
|
|
980
|
+
applied.add(row.actionId);
|
|
981
|
+
}
|
|
982
|
+
const remaining = orderBootstrapActions(ordered.filter((action) => !applied.has(action.id)), applied);
|
|
983
|
+
for (const action of remaining) {
|
|
984
|
+
for (const dependency of action.dependsOn ?? []) {
|
|
985
|
+
if (!applied.has(dependency) &&
|
|
986
|
+
!remaining.some((candidate) => candidate.id === dependency))
|
|
987
|
+
throw new Error(`Bootstrap recovery journal is missing dependency '${dependency}' for '${action.id}'`);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return executeBootstrapPlan({ plan: journal.plan, approvedPlanHash: journal.plan.planHash }, context, true, remaining, (journal.results ?? []).flatMap((row) => row &&
|
|
991
|
+
typeof row.actionId === "string" &&
|
|
992
|
+
(row.outcome === "applied" ||
|
|
993
|
+
row.outcome === "failed" ||
|
|
994
|
+
row.outcome === "skipped") &&
|
|
995
|
+
typeof row.detail === "string"
|
|
996
|
+
? row.outcome === "applied"
|
|
997
|
+
? [
|
|
998
|
+
{
|
|
999
|
+
actionId: row.actionId,
|
|
1000
|
+
outcome: row.outcome,
|
|
1001
|
+
detail: row.detail,
|
|
1002
|
+
},
|
|
1003
|
+
]
|
|
1004
|
+
: []
|
|
1005
|
+
: []));
|
|
1006
|
+
}
|
|
1007
|
+
return executeSourceRecovery(args, context);
|
|
1008
|
+
}
|
|
1009
|
+
if (isBootstrapApplyArgs(args))
|
|
1010
|
+
return executeBootstrapPlan(args, context);
|
|
1011
|
+
if (isMigrationApplyArgs(args)) {
|
|
1012
|
+
return applyMigrationPlan(args, context);
|
|
1013
|
+
}
|
|
1014
|
+
if (isEntityDeletionApplyArgs(args)) {
|
|
1015
|
+
validateEntityDeletionPlan(args);
|
|
1016
|
+
const sourceWrites = await applySourceWrites(context, args.plan.sourceWrites ?? [], args.plan.planHash);
|
|
1017
|
+
const operationContext = {
|
|
1018
|
+
...context,
|
|
1019
|
+
sourceFirst: false,
|
|
1020
|
+
sourcePlanApplication: true,
|
|
1021
|
+
};
|
|
1022
|
+
let payload;
|
|
1023
|
+
const nextActions = [];
|
|
1024
|
+
let status;
|
|
1025
|
+
try {
|
|
1026
|
+
const result = await executeDelete({ ids: args.plan.entityIds }, operationContext);
|
|
1027
|
+
payload = result.structuredContent;
|
|
1028
|
+
await markSourceJournal(operationContext, sourceWrites.journalId, "compiled_published");
|
|
1029
|
+
}
|
|
1030
|
+
catch (error) {
|
|
1031
|
+
status = "committed_with_repairs";
|
|
1032
|
+
nextActions.push({
|
|
1033
|
+
operation: "kb_apply_plan",
|
|
1034
|
+
input: { recoveryJournalId: sourceWrites.journalId },
|
|
1035
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
1036
|
+
reason: "The deletion source commit is authoritative but compiled retraction failed; repair from the journal without retrying deletion.",
|
|
1037
|
+
required: true,
|
|
1038
|
+
});
|
|
1039
|
+
await markSourceJournal(operationContext, sourceWrites.journalId, "repair_required");
|
|
1040
|
+
payload = {
|
|
1041
|
+
deleted: 0,
|
|
1042
|
+
skipped: args.plan.entityIds.length,
|
|
1043
|
+
errors: [error instanceof Error ? error.message : String(error)],
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
content: [
|
|
1048
|
+
{
|
|
1049
|
+
type: "text",
|
|
1050
|
+
text: `Applied entity deletion plan ${args.plan.planHash.slice(0, 12)}.`,
|
|
1051
|
+
},
|
|
1052
|
+
],
|
|
1053
|
+
structuredContent: {
|
|
1054
|
+
version: "kibi.entity-deletion-apply-result.v1",
|
|
1055
|
+
outcome: "applied",
|
|
1056
|
+
planHash: args.plan.planHash,
|
|
1057
|
+
deleted: payload.deleted,
|
|
1058
|
+
sourcePaths: sourceWrites.paths,
|
|
1059
|
+
...(sourceWrites.journalId !== null
|
|
1060
|
+
? { recoveryJournalId: sourceWrites.journalId }
|
|
1061
|
+
: {}),
|
|
1062
|
+
...(status !== undefined ? { status, nextActions } : {}),
|
|
1063
|
+
},
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
validateCompilePlanShape(args);
|
|
1067
|
+
const prolog = context.prolog ?? (await context.ensureProlog?.()) ?? undefined;
|
|
1068
|
+
if (!prolog)
|
|
1069
|
+
throw new Error("Apply plan requires a Prolog runtime");
|
|
1070
|
+
// Compile plans carry the complete, hash-bound sourceWrites set. Apply the
|
|
1071
|
+
// compiled entity steps against the staged source snapshot without asking
|
|
1072
|
+
// each step to independently select a document target; otherwise a plan
|
|
1073
|
+
// with multiple authored entity kinds could be rejected for an ambiguous
|
|
1074
|
+
// per-entity path after its source batch has already been validated.
|
|
1075
|
+
const operationContext = context.prolog
|
|
1076
|
+
? { ...context, sourceFirst: false }
|
|
1077
|
+
: { ...context, prolog, sourceFirst: false };
|
|
1078
|
+
const statusResult = await executeStatus({}, operationContext);
|
|
1079
|
+
const status = statusResult.structuredContent;
|
|
1080
|
+
if (!status)
|
|
1081
|
+
throw new Error("Apply plan failed: status query returned no payload");
|
|
1082
|
+
if (status.branch !== args.plan.expected.branch)
|
|
1083
|
+
throw new Error("Apply plan failed: branch changed since compilation");
|
|
1084
|
+
if (status.snapshotId !== args.plan.expected.kbSnapshotId)
|
|
1085
|
+
throw new Error("Apply plan failed: KB snapshot changed since compilation");
|
|
1086
|
+
const workspace = await readWorkspaceSnapshot(operationContext);
|
|
1087
|
+
if (!workspace.available)
|
|
1088
|
+
throw new Error(`Apply plan failed: ${workspace.error}`);
|
|
1089
|
+
if (workspace.snapshot.hash !== args.plan.expected.workspaceSnapshot)
|
|
1090
|
+
throw new Error("Apply plan failed: workspace snapshot changed since compilation");
|
|
1091
|
+
const sourceHashesChecked = await validateSources(operationContext, args.plan.expected.sourceHashes);
|
|
1092
|
+
const steps = args.plan.steps.map((step) => asUpsert(step));
|
|
1093
|
+
const sourceWrites = await applySourceWrites(operationContext, args.plan.sourceWrites, args.plan.planHash);
|
|
1094
|
+
const notes = [
|
|
1095
|
+
"Plan steps and tracked source writes are validated before sequential application; source writes are journaled for replay.",
|
|
1096
|
+
];
|
|
1097
|
+
let changedEntities = 0;
|
|
1098
|
+
let changedRelationships = 0;
|
|
1099
|
+
const effectFailures = [];
|
|
1100
|
+
const nextActions = [];
|
|
1101
|
+
let compiledCommit = false;
|
|
1102
|
+
try {
|
|
1103
|
+
for (const step of steps) {
|
|
1104
|
+
const result = await executeUpsert(step, operationContext);
|
|
1105
|
+
const payload = result.structuredContent;
|
|
1106
|
+
if (payload && typeof payload === "object") {
|
|
1107
|
+
const row = payload;
|
|
1108
|
+
changedEntities += Number(row.created ?? 0) + Number(row.updated ?? 0);
|
|
1109
|
+
changedRelationships += Number(row.relationships_created ?? 0);
|
|
1110
|
+
const details = payload;
|
|
1111
|
+
if (Array.isArray(details.effectFailures)) {
|
|
1112
|
+
effectFailures.push(...details.effectFailures.filter(isRecord));
|
|
1113
|
+
}
|
|
1114
|
+
if (Array.isArray(details.nextActions)) {
|
|
1115
|
+
nextActions.push(...details.nextActions.filter(isRecord));
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
compiledCommit = true;
|
|
1120
|
+
}
|
|
1121
|
+
catch (error) {
|
|
1122
|
+
if (sourceWrites.journalId !== null) {
|
|
1123
|
+
effectFailures.push({
|
|
1124
|
+
kind: "compiled-store",
|
|
1125
|
+
errorCode: "DERIVED_COMMIT_FAILED",
|
|
1126
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
1127
|
+
});
|
|
1128
|
+
nextActions.push({
|
|
1129
|
+
operation: "kb_apply_plan",
|
|
1130
|
+
input: { recoveryJournalId: sourceWrites.journalId },
|
|
1131
|
+
reason: "Authoritative source files are committed but compiled effects failed; replay the recovery journal instead of retrying the original mutation.",
|
|
1132
|
+
required: true,
|
|
1133
|
+
});
|
|
1134
|
+
await markSourceJournal(operationContext, sourceWrites.journalId, "repair_required");
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
throw error;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (compiledCommit) {
|
|
1141
|
+
await markSourceJournal(operationContext, sourceWrites.journalId, "compiled_published");
|
|
1142
|
+
}
|
|
1143
|
+
// Everything before this point is authoritative. A status/workspace
|
|
1144
|
+
// readback failure therefore cannot turn the operation into a retryable
|
|
1145
|
+
// mutation: return a repairable partial completion with deterministic next
|
|
1146
|
+
// actions instead.
|
|
1147
|
+
let finalStatus;
|
|
1148
|
+
let finalWorkspace;
|
|
1149
|
+
let postCommitFailure;
|
|
1150
|
+
try {
|
|
1151
|
+
const finalStatusResult = await executeStatus({}, operationContext);
|
|
1152
|
+
finalStatus = finalStatusResult.structuredContent;
|
|
1153
|
+
if (!finalStatus) {
|
|
1154
|
+
throw new Error("final status query returned no payload");
|
|
1155
|
+
}
|
|
1156
|
+
finalWorkspace = await readWorkspaceSnapshot(operationContext);
|
|
1157
|
+
if (!finalWorkspace.available) {
|
|
1158
|
+
throw new Error(finalWorkspace.error ?? "final workspace snapshot unavailable");
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
catch (error) {
|
|
1162
|
+
postCommitFailure = error instanceof Error ? error.message : String(error);
|
|
1163
|
+
effectFailures.push({
|
|
1164
|
+
kind: "post-commit-readback",
|
|
1165
|
+
errorCode: "POST_COMMIT_READBACK_FAILED",
|
|
1166
|
+
detail: postCommitFailure,
|
|
1167
|
+
});
|
|
1168
|
+
nextActions.push({
|
|
1169
|
+
operation: "kb_status",
|
|
1170
|
+
reason: "The plan committed its source and compiled mutations, but final status readback failed; inspect the committed snapshot before any repair.",
|
|
1171
|
+
required: true,
|
|
1172
|
+
}, {
|
|
1173
|
+
operation: "kb_check",
|
|
1174
|
+
reason: "After status is readable, run the consistency checks and follow their typed repair actions.",
|
|
1175
|
+
required: true,
|
|
1176
|
+
});
|
|
1177
|
+
await markSourceJournal(operationContext, sourceWrites.journalId, "repair_required");
|
|
1178
|
+
}
|
|
1179
|
+
const finalSnapshots = finalStatus !== undefined && finalWorkspace?.available === true
|
|
1180
|
+
? {
|
|
1181
|
+
branch: finalStatus.branch,
|
|
1182
|
+
kbSnapshotId: finalStatus.snapshotId,
|
|
1183
|
+
workspaceSnapshot: finalWorkspace.snapshot.hash,
|
|
1184
|
+
}
|
|
1185
|
+
: {
|
|
1186
|
+
branch: args.plan.expected.branch,
|
|
1187
|
+
kbSnapshotId: args.plan.expected.kbSnapshotId,
|
|
1188
|
+
workspaceSnapshot: args.plan.expected.workspaceSnapshot,
|
|
1189
|
+
};
|
|
1190
|
+
const payload = {
|
|
1191
|
+
version: PLAN_APPLY_RESULT_VERSION,
|
|
1192
|
+
outcome: "applied",
|
|
1193
|
+
planHash: args.plan.planHash,
|
|
1194
|
+
changedEntities,
|
|
1195
|
+
changedRelationships,
|
|
1196
|
+
changedPaths: sourceWrites.paths,
|
|
1197
|
+
finalSnapshots,
|
|
1198
|
+
validationSummary: {
|
|
1199
|
+
stepsValidated: steps.length,
|
|
1200
|
+
stepsApplied: steps.length,
|
|
1201
|
+
sourceHashesChecked,
|
|
1202
|
+
notes,
|
|
1203
|
+
},
|
|
1204
|
+
recoveryJournalId: sourceWrites.journalId,
|
|
1205
|
+
...(effectFailures.length > 0 || postCommitFailure !== undefined
|
|
1206
|
+
? {
|
|
1207
|
+
status: "committed_with_repairs",
|
|
1208
|
+
effectFailures,
|
|
1209
|
+
nextActions,
|
|
1210
|
+
}
|
|
1211
|
+
: {}),
|
|
1212
|
+
};
|
|
1213
|
+
return {
|
|
1214
|
+
content: [
|
|
1215
|
+
{
|
|
1216
|
+
type: "text",
|
|
1217
|
+
text: `Applied plan ${args.plan.planHash.slice(0, 12)} with ${steps.length} sequential step(s).`,
|
|
1218
|
+
},
|
|
1219
|
+
],
|
|
1220
|
+
structuredContent: payload,
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
async function applyMigrationPlan(args, context) {
|
|
1224
|
+
const actions = topologicalActions(validateMigrationPlanShape(args));
|
|
1225
|
+
const initialStatus = (await executeStatus({}, context)).structuredContent;
|
|
1226
|
+
if (!initialStatus)
|
|
1227
|
+
throw new Error("Migration apply failed: status query returned no payload");
|
|
1228
|
+
if (args.plan.expected.branch !== null &&
|
|
1229
|
+
initialStatus.branch !== args.plan.expected.branch)
|
|
1230
|
+
throw new Error("Migration apply failed: active branch changed since planning");
|
|
1231
|
+
if (args.plan.expected.kbBranch !== null &&
|
|
1232
|
+
initialStatus.branch !== args.plan.expected.kbBranch)
|
|
1233
|
+
throw new Error("Migration apply failed: KB branch changed since planning");
|
|
1234
|
+
if (args.plan.expected.kbSnapshotId !== null &&
|
|
1235
|
+
initialStatus.snapshotId !== args.plan.expected.kbSnapshotId)
|
|
1236
|
+
throw new Error("Migration apply failed: KB snapshot changed since planning");
|
|
1237
|
+
if (args.plan.expected.workspaceSnapshot !== null) {
|
|
1238
|
+
const workspace = await readWorkspaceSnapshot(context);
|
|
1239
|
+
if (!workspace.available ||
|
|
1240
|
+
workspace.snapshot.hash !== args.plan.expected.workspaceSnapshot)
|
|
1241
|
+
throw new Error("Migration apply failed: workspace snapshot changed since planning");
|
|
1242
|
+
}
|
|
1243
|
+
if (args.plan.expected.configHash !== null) {
|
|
1244
|
+
const currentConfig = readMigrationConfigStatus(context.workspaceRoot);
|
|
1245
|
+
if (currentConfig.configHash !== args.plan.expected.configHash)
|
|
1246
|
+
throw new Error("Migration apply failed: config changed since planning");
|
|
1247
|
+
}
|
|
1248
|
+
const results = [];
|
|
1249
|
+
let failed = false;
|
|
1250
|
+
for (const action of actions) {
|
|
1251
|
+
if (failed) {
|
|
1252
|
+
results.push({
|
|
1253
|
+
actionId: action.id,
|
|
1254
|
+
outcome: "skipped",
|
|
1255
|
+
detail: "Skipped after an earlier action failed.",
|
|
1256
|
+
});
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
try {
|
|
1260
|
+
await applyMigrationAction(action, context);
|
|
1261
|
+
results.push({
|
|
1262
|
+
actionId: action.id,
|
|
1263
|
+
outcome: "applied",
|
|
1264
|
+
detail: `Applied ${action.code}.`,
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
catch (error) {
|
|
1268
|
+
failed = true;
|
|
1269
|
+
results.push({
|
|
1270
|
+
actionId: action.id,
|
|
1271
|
+
outcome: "failed",
|
|
1272
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
const finalStatus = (await executeStatus({}, context)).structuredContent;
|
|
1277
|
+
if (!finalStatus)
|
|
1278
|
+
throw new Error("Migration apply failed: final status query returned no payload");
|
|
1279
|
+
const finalWorkspace = await readWorkspaceSnapshot(context);
|
|
1280
|
+
if (!finalWorkspace.available)
|
|
1281
|
+
throw new Error(`Migration apply failed: ${finalWorkspace.error}`);
|
|
1282
|
+
let remainingPlan;
|
|
1283
|
+
let coverageSummary;
|
|
1284
|
+
if (!failed && finalStatus.branchStore?.state === "healthy") {
|
|
1285
|
+
try {
|
|
1286
|
+
const prolog = context.prolog ?? (await context.ensureProlog?.());
|
|
1287
|
+
if (prolog !== undefined) {
|
|
1288
|
+
const operationContext = context.prolog
|
|
1289
|
+
? context
|
|
1290
|
+
: { ...context, prolog };
|
|
1291
|
+
const [{ executeCheck }, { executeCoverage }, { mergeMigrationPlans }] = await Promise.all([
|
|
1292
|
+
import("../../public/operations/check-executor.js"),
|
|
1293
|
+
import("../../public/operations/specs/reporting.js"),
|
|
1294
|
+
import("../../public/operations/migration-plan.js"),
|
|
1295
|
+
]);
|
|
1296
|
+
const check = await executeCheck({}, operationContext);
|
|
1297
|
+
const coverage = await executeCoverage({ by: "req", limit: 10_000, offset: 0 }, operationContext);
|
|
1298
|
+
const symbolCoverage = await executeCoverage({ by: "symbol", limit: 10_000, offset: 0 }, operationContext);
|
|
1299
|
+
const fragments = [
|
|
1300
|
+
check.structuredContent?.migrationPlan,
|
|
1301
|
+
coverage.structuredContent?.migrationPlan,
|
|
1302
|
+
symbolCoverage.structuredContent?.migrationPlan,
|
|
1303
|
+
].filter((value) => value !== undefined);
|
|
1304
|
+
if (fragments.length > 0)
|
|
1305
|
+
remainingPlan = mergeMigrationPlans(fragments);
|
|
1306
|
+
coverageSummary = coverage.structuredContent?.summary;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
catch (error) {
|
|
1310
|
+
failed = true;
|
|
1311
|
+
results.push({
|
|
1312
|
+
actionId: "post-apply-readback",
|
|
1313
|
+
outcome: "failed",
|
|
1314
|
+
detail: `Post-apply check/coverage readback failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
const outcome = failed
|
|
1319
|
+
? results.some((result) => result.outcome === "applied")
|
|
1320
|
+
? "partially_applied"
|
|
1321
|
+
: "reconciliation_required"
|
|
1322
|
+
: "applied";
|
|
1323
|
+
const kbState = finalStatus.branchAttachment?.migrationRequired
|
|
1324
|
+
? "legacy_compat"
|
|
1325
|
+
: finalStatus.syncState === "stale"
|
|
1326
|
+
? "stale"
|
|
1327
|
+
: finalStatus.dirty
|
|
1328
|
+
? "dirty"
|
|
1329
|
+
: finalStatus.syncState === "fresh"
|
|
1330
|
+
? "clean_fresh"
|
|
1331
|
+
: "not_evaluated";
|
|
1332
|
+
const verificationState = finalStatus.verificationSnapshotAvailable === false
|
|
1333
|
+
? "unavailable"
|
|
1334
|
+
: finalStatus.verificationSnapshotDirty === true
|
|
1335
|
+
? "dirty"
|
|
1336
|
+
: finalStatus.verificationSnapshotDirty === false
|
|
1337
|
+
? "fresh"
|
|
1338
|
+
: "not_evaluated";
|
|
1339
|
+
const proven = coverageSummary?.proofProven;
|
|
1340
|
+
const missing = coverageSummary?.proofMissing;
|
|
1341
|
+
const proofState = typeof proven !== "number" || typeof missing !== "number"
|
|
1342
|
+
? "not_evaluated"
|
|
1343
|
+
: proven > 0 && missing === 0
|
|
1344
|
+
? "proven"
|
|
1345
|
+
: proven > 0
|
|
1346
|
+
? "mixed"
|
|
1347
|
+
: "unresolved";
|
|
1348
|
+
const payload = {
|
|
1349
|
+
version: "kibi.migration-apply-result.v1",
|
|
1350
|
+
outcome,
|
|
1351
|
+
planHash: args.plan.planHash,
|
|
1352
|
+
actionResults: results,
|
|
1353
|
+
finalSnapshots: {
|
|
1354
|
+
branch: finalStatus.branch,
|
|
1355
|
+
kbSnapshotId: finalStatus.snapshotId,
|
|
1356
|
+
workspaceSnapshot: finalWorkspace.snapshot.hash,
|
|
1357
|
+
},
|
|
1358
|
+
notes: [
|
|
1359
|
+
"Migration actions were applied sequentially; rerun kibi status, check, and complete coverage to obtain the next plan.",
|
|
1360
|
+
],
|
|
1361
|
+
...(remainingPlan !== undefined ? { remainingPlan } : {}),
|
|
1362
|
+
closeout: {
|
|
1363
|
+
taskOutcome: outcome === "applied"
|
|
1364
|
+
? "complete"
|
|
1365
|
+
: outcome === "reconciliation_required"
|
|
1366
|
+
? "blocked"
|
|
1367
|
+
: "interim",
|
|
1368
|
+
kbState,
|
|
1369
|
+
verificationState,
|
|
1370
|
+
proofState,
|
|
1371
|
+
limitationDisposition: "not_applicable",
|
|
1372
|
+
},
|
|
1373
|
+
};
|
|
1374
|
+
return {
|
|
1375
|
+
content: [
|
|
1376
|
+
{
|
|
1377
|
+
type: "text",
|
|
1378
|
+
text: `${outcome === "applied" ? "Applied" : "Stopped after"} migration plan ${args.plan.planHash.slice(0, 12)}.`,
|
|
1379
|
+
},
|
|
1380
|
+
],
|
|
1381
|
+
structuredContent: payload,
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
async function applyMigrationAction(action, context) {
|
|
1385
|
+
switch (action.code) {
|
|
1386
|
+
case "legacy_branch_storage":
|
|
1387
|
+
if (action.invocation.kind !== "cli")
|
|
1388
|
+
throw new Error("Legacy branch migration action is missing its explicit source identity.");
|
|
1389
|
+
{
|
|
1390
|
+
const argv = action.invocation.command_argv;
|
|
1391
|
+
const fromIndex = argv.indexOf("--from");
|
|
1392
|
+
const toIndex = argv.indexOf("--to");
|
|
1393
|
+
const from = fromIndex >= 0 ? argv[fromIndex + 1] : undefined;
|
|
1394
|
+
const to = toIndex >= 0 ? argv[toIndex + 1] : undefined;
|
|
1395
|
+
if (!from || !to)
|
|
1396
|
+
throw new Error("Legacy branch migration action requires explicit --from and --to.");
|
|
1397
|
+
await branchMigrateCommand({
|
|
1398
|
+
from,
|
|
1399
|
+
to,
|
|
1400
|
+
apply: true,
|
|
1401
|
+
workspaceRoot: context.workspaceRoot,
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
return;
|
|
1405
|
+
case "missing_exact_branch_store":
|
|
1406
|
+
await branchEnsureCommand({ workspaceRoot: context.workspaceRoot });
|
|
1407
|
+
return;
|
|
1408
|
+
case "damaged_exact_branch_store":
|
|
1409
|
+
await branchRecoverCommand({
|
|
1410
|
+
apply: true,
|
|
1411
|
+
workspaceRoot: context.workspaceRoot,
|
|
1412
|
+
});
|
|
1413
|
+
return;
|
|
1414
|
+
case "schema_version_upgrade":
|
|
1415
|
+
case "invalid_schema_version":
|
|
1416
|
+
case "legacy_storage_migration":
|
|
1417
|
+
if ((await migrateCommand({
|
|
1418
|
+
yes: true,
|
|
1419
|
+
workspaceRoot: context.workspaceRoot,
|
|
1420
|
+
initializeMissingConfig: true,
|
|
1421
|
+
})).exitCode !== 0)
|
|
1422
|
+
throw new Error("Schema migration did not complete successfully.");
|
|
1423
|
+
return;
|
|
1424
|
+
case "symbol_refresh_coordinates":
|
|
1425
|
+
case "coverage_source_coordinates": {
|
|
1426
|
+
const result = await syncCommand({
|
|
1427
|
+
refreshSymbolCoordinates: true,
|
|
1428
|
+
workspaceRoot: context.workspaceRoot,
|
|
1429
|
+
});
|
|
1430
|
+
if (!result.success)
|
|
1431
|
+
throw new Error("Coordinate refresh did not complete successfully.");
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
default:
|
|
1435
|
+
throw new Error(`Migration action '${action.code}' has no automatic executor.`);
|
|
1436
|
+
}
|
|
1437
|
+
}
|