kibi-cli 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-json-command.d.ts +6 -0
- package/dist/cli-json-command.d.ts.map +1 -1
- package/dist/cli-json-command.js +39 -11
- package/dist/cli-operation-loader.d.ts.map +1 -1
- package/dist/cli-operation-loader.js +7 -0
- package/dist/cli-operation-metadata.d.ts +13 -5
- package/dist/cli-operation-metadata.d.ts.map +1 -1
- package/dist/cli-operation-metadata.js +15 -5
- package/dist/cli-protocol.d.ts +14 -0
- package/dist/cli-protocol.d.ts.map +1 -1
- package/dist/cli-protocol.js +31 -2
- package/dist/cli-register-maintenance.d.ts.map +1 -1
- package/dist/cli-register-maintenance.js +7 -0
- package/dist/cli-register-proof.d.ts.map +1 -1
- package/dist/cli-register-proof.js +13 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +11 -2
- package/dist/commands/discovery-shared.d.ts.map +1 -1
- package/dist/commands/discovery-shared.js +8 -2
- package/dist/commands/engine.d.ts.map +1 -1
- package/dist/commands/engine.js +4 -2
- package/dist/commands/gc.d.ts.map +1 -1
- package/dist/commands/gc.js +7 -0
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +0 -1
- package/dist/commands/merge-driver.d.ts +31 -0
- package/dist/commands/merge-driver.d.ts.map +1 -0
- package/dist/commands/merge-driver.js +268 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +281 -14
- package/dist/commands/proof-compact.d.ts +9 -0
- package/dist/commands/proof-compact.d.ts.map +1 -0
- package/dist/commands/proof-compact.js +58 -0
- package/dist/commands/prove.d.ts +38 -0
- package/dist/commands/prove.d.ts.map +1 -1
- package/dist/commands/prove.js +222 -47
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +61 -16
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +42 -16
- package/dist/diagnostics.js +1 -1
- package/dist/engine-limits.d.ts +74 -0
- package/dist/engine-limits.d.ts.map +1 -0
- package/dist/engine-limits.js +201 -0
- package/dist/engine.d.ts +10 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +57 -9
- package/dist/extractors/manifest.d.ts +8 -0
- package/dist/extractors/manifest.d.ts.map +1 -1
- package/dist/extractors/manifest.js +34 -5
- package/dist/extractors/markdown.d.ts +8 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +57 -0
- package/dist/extractors/relationships.d.ts.map +1 -1
- package/dist/extractors/relationships.js +2 -0
- package/dist/intent-search.d.ts +4 -1
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +186 -33
- package/dist/operations/bootstrap/candidate-helpers.d.ts.map +1 -1
- package/dist/operations/bootstrap/candidate-helpers.js +8 -1
- package/dist/operations/bootstrap/candidates.d.ts.map +1 -1
- package/dist/operations/bootstrap/candidates.js +55 -39
- package/dist/operations/bootstrap/discovery.js +1 -1
- package/dist/operations/bootstrap/generate.d.ts +2 -0
- package/dist/operations/bootstrap/generate.d.ts.map +1 -1
- package/dist/operations/bootstrap/generate.js +107 -37
- package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
- package/dist/operations/bootstrap/guidance-actions.js +3 -0
- package/dist/operations/bootstrap/guidance.d.ts.map +1 -1
- package/dist/operations/bootstrap/guidance.js +7 -1
- package/dist/operations/bootstrap/intent-claims.d.ts +17 -0
- package/dist/operations/bootstrap/intent-claims.d.ts.map +1 -0
- package/dist/operations/bootstrap/intent-claims.js +103 -0
- package/dist/operations/bootstrap/markdown-candidates.d.ts +2 -0
- package/dist/operations/bootstrap/markdown-candidates.d.ts.map +1 -1
- package/dist/operations/bootstrap/markdown-candidates.js +32 -10
- package/dist/operations/bootstrap/presentation.d.ts +2 -0
- package/dist/operations/bootstrap/presentation.d.ts.map +1 -1
- package/dist/operations/bootstrap/presentation.js +66 -3
- package/dist/operations/bootstrap/requirement-claims.d.ts +1 -0
- package/dist/operations/bootstrap/requirement-claims.d.ts.map +1 -1
- package/dist/operations/bootstrap/requirement-claims.js +13 -1
- package/dist/operations/bootstrap/types.d.ts +28 -0
- package/dist/operations/bootstrap/types.d.ts.map +1 -1
- package/dist/operations/bootstrap/types.js +16 -0
- package/dist/operations/bootstrap/validation.d.ts +4 -0
- package/dist/operations/bootstrap/validation.d.ts.map +1 -0
- package/dist/operations/bootstrap/validation.js +20 -0
- package/dist/operations/migration/inventory-rederive.d.ts +81 -0
- package/dist/operations/migration/inventory-rederive.d.ts.map +1 -0
- package/dist/operations/migration/inventory-rederive.js +484 -0
- package/dist/operations/migration/kb-sources.d.ts +53 -0
- package/dist/operations/migration/kb-sources.d.ts.map +1 -0
- package/dist/operations/migration/kb-sources.js +233 -0
- package/dist/operations/migration/origin-backfill.d.ts +39 -0
- package/dist/operations/migration/origin-backfill.d.ts.map +1 -0
- package/dist/operations/migration/origin-backfill.js +164 -0
- package/dist/operations/migration/polarity-values.d.ts +10 -0
- package/dist/operations/migration/polarity-values.d.ts.map +1 -0
- package/dist/operations/migration/polarity-values.js +60 -0
- package/dist/operations/migration/schema6.d.ts +59 -0
- package/dist/operations/migration/schema6.d.ts.map +1 -0
- package/dist/operations/migration/schema6.js +379 -0
- package/dist/operations/migration/source-paths.d.ts +131 -0
- package/dist/operations/migration/source-paths.d.ts.map +1 -0
- package/dist/operations/migration/source-paths.js +308 -0
- package/dist/operations/migration/superseded-closure.d.ts +54 -0
- package/dist/operations/migration/superseded-closure.d.ts.map +1 -0
- package/dist/operations/migration/superseded-closure.js +187 -0
- package/dist/operations/modeling/logic-modeling.d.ts.map +1 -1
- package/dist/operations/modeling/logic-modeling.js +3 -5
- package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
- package/dist/operations/modeling/model-requirement.js +225 -67
- package/dist/operations/mutation/contradictions.d.ts +16 -0
- package/dist/operations/mutation/contradictions.d.ts.map +1 -1
- package/dist/operations/mutation/contradictions.js +32 -0
- package/dist/operations/mutation/delete.d.ts.map +1 -1
- package/dist/operations/mutation/delete.js +15 -7
- package/dist/operations/mutation/origin.d.ts +21 -0
- package/dist/operations/mutation/origin.d.ts.map +1 -0
- package/dist/operations/mutation/origin.js +78 -0
- package/dist/operations/mutation/plan-apply-journal.d.ts +182 -0
- package/dist/operations/mutation/plan-apply-journal.d.ts.map +1 -0
- package/dist/operations/mutation/plan-apply-journal.js +613 -0
- package/dist/operations/mutation/predicate-vocabulary-guard.d.ts +3 -1
- package/dist/operations/mutation/predicate-vocabulary-guard.d.ts.map +1 -1
- package/dist/operations/mutation/predicate-vocabulary-guard.js +13 -2
- package/dist/operations/mutation/relationships.d.ts +5 -5
- package/dist/operations/mutation/relationships.d.ts.map +1 -1
- package/dist/operations/mutation/relationships.js +31 -11
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +3 -1
- package/dist/operations/mutation/source-authoring.d.ts +6 -0
- package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
- package/dist/operations/mutation/source-authoring.js +7 -1
- package/dist/operations/mutation/symbol-refresh.d.ts.map +1 -1
- package/dist/operations/mutation/symbol-refresh.js +4 -1
- package/dist/operations/mutation/types.d.ts +14 -0
- package/dist/operations/mutation/types.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.d.ts +30 -3
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +105 -35
- package/dist/operations/mutation/validate-upsert.d.ts +6 -0
- package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
- package/dist/operations/mutation/validate-upsert.js +34 -30
- package/dist/operations/planning/apply-plan.d.ts.map +1 -1
- package/dist/operations/planning/apply-plan.js +751 -144
- package/dist/operations/planning/compile-intent.d.ts +34 -1
- package/dist/operations/planning/compile-intent.d.ts.map +1 -1
- package/dist/operations/planning/compile-intent.js +314 -98
- package/dist/operations/proof/code-scope.d.ts +28 -0
- package/dist/operations/proof/code-scope.d.ts.map +1 -1
- package/dist/operations/proof/code-scope.js +72 -0
- package/dist/operations/proof/compact-receipts.d.ts +49 -0
- package/dist/operations/proof/compact-receipts.d.ts.map +1 -0
- package/dist/operations/proof/compact-receipts.js +160 -0
- package/dist/operations/proof/ingest-proof.d.ts +2 -0
- package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
- package/dist/operations/proof/ingest-proof.js +44 -23
- package/dist/operations/proof/prune-receipts.d.ts.map +1 -1
- package/dist/operations/proof/prune-receipts.js +5 -9
- package/dist/operations/proof/receipt-compaction.d.ts +48 -0
- package/dist/operations/proof/receipt-compaction.d.ts.map +1 -0
- package/dist/operations/proof/receipt-compaction.js +121 -0
- package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analysis-receipt.js +12 -1
- package/dist/operations/semantic-advisor/analyze-prose.d.ts +2 -1
- package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.js +51 -5
- package/dist/operations/semantic-advisor/clauses.d.ts +1 -0
- package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/clauses.js +11 -1
- package/dist/operations/semantic-advisor/conditional-rules.d.ts +33 -0
- package/dist/operations/semantic-advisor/conditional-rules.d.ts.map +1 -0
- package/dist/operations/semantic-advisor/conditional-rules.js +262 -0
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts +3 -1
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/ingestion-boundary.js +14 -6
- package/dist/operations/semantic-advisor/plugin-orchestration.js +1 -1
- package/dist/prolog/codec.d.ts.map +1 -1
- package/dist/prolog/codec.js +2 -1
- package/dist/prolog.d.ts +9 -0
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +56 -10
- package/dist/proof/command-test-report.d.ts +74 -0
- package/dist/proof/command-test-report.d.ts.map +1 -0
- package/dist/proof/command-test-report.js +206 -0
- 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/diagnostic-usage.d.ts +14 -0
- package/dist/public/diagnostic-usage.d.ts.map +1 -1
- package/dist/public/diagnostic-usage.js +55 -1
- package/dist/public/entity-origin.d.ts +69 -0
- package/dist/public/entity-origin.d.ts.map +1 -0
- package/dist/public/entity-origin.js +142 -0
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
- package/dist/public/impact/full-kb-quality.js +1 -0
- package/dist/public/impact/requirement-quality.js +1 -1
- package/dist/public/operations/catalog.d.ts +203 -20
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/catalog.js +3 -0
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +8 -0
- package/dist/public/operations/contracts.d.ts +0 -5
- package/dist/public/operations/contracts.d.ts.map +1 -1
- package/dist/public/operations/contracts.js +63 -14
- package/dist/public/operations/discovery-entities.d.ts +15 -0
- package/dist/public/operations/discovery-entities.d.ts.map +1 -1
- package/dist/public/operations/discovery-entities.js +59 -0
- package/dist/public/operations/discovery-executors.d.ts +6 -1
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +65 -6
- package/dist/public/operations/index.d.ts +1 -0
- package/dist/public/operations/index.d.ts.map +1 -1
- package/dist/public/operations/index.js +1 -0
- package/dist/public/operations/migration-plan.d.ts +5 -0
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +26 -1
- package/dist/public/operations/node-ports.d.ts.map +1 -1
- package/dist/public/operations/node-ports.js +24 -3
- package/dist/public/operations/origin-review.d.ts +51 -0
- package/dist/public/operations/origin-review.d.ts.map +1 -0
- package/dist/public/operations/origin-review.js +307 -0
- package/dist/public/operations/predicate-schema-conformance.js +1 -1
- package/dist/public/operations/repair-plan.d.ts.map +1 -1
- package/dist/public/operations/repair-plan.js +4 -0
- package/dist/public/operations/result-envelope.d.ts.map +1 -1
- package/dist/public/operations/result-envelope.js +17 -5
- package/dist/public/operations/runtime-types.d.ts +9 -0
- package/dist/public/operations/runtime-types.d.ts.map +1 -1
- package/dist/public/operations/schema6-check-actions.d.ts +127 -0
- package/dist/public/operations/schema6-check-actions.d.ts.map +1 -0
- package/dist/public/operations/schema6-check-actions.js +575 -0
- package/dist/public/operations/source-path-dangling.d.ts +17 -0
- package/dist/public/operations/source-path-dangling.d.ts.map +1 -0
- package/dist/public/operations/source-path-dangling.js +66 -0
- package/dist/public/operations/specs/bootstrap.d.ts +73 -1
- package/dist/public/operations/specs/bootstrap.d.ts.map +1 -1
- package/dist/public/operations/specs/bootstrap.js +66 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
- package/dist/public/operations/specs/check-rules.generated.js +15 -1
- package/dist/public/operations/specs/check.d.ts +2 -2
- package/dist/public/operations/specs/check.js +1 -1
- package/dist/public/operations/specs/composite.d.ts +351 -0
- package/dist/public/operations/specs/composite.d.ts.map +1 -0
- package/dist/public/operations/specs/composite.js +96 -0
- package/dist/public/operations/specs/discovery.d.ts +8 -3
- package/dist/public/operations/specs/discovery.d.ts.map +1 -1
- package/dist/public/operations/specs/discovery.js +8 -3
- package/dist/public/operations/specs/modeling.d.ts +2 -2
- package/dist/public/operations/specs/modeling.js +2 -2
- package/dist/public/operations/specs/mutation-schemas.d.ts +73 -8
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +43 -7
- package/dist/public/operations/specs/mutation.d.ts +1 -1
- package/dist/public/operations/specs/mutation.js +1 -1
- package/dist/public/operations/specs/planning.d.ts +2 -2
- package/dist/public/operations/specs/planning.js +4 -4
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +13 -26
- package/dist/public/operations/specs/upsert.d.ts +79 -9
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/operations/specs/upsert.js +6 -1
- package/dist/public/operations/symbol-repair-plan.d.ts.map +1 -1
- package/dist/public/operations/symbol-repair-plan.js +3 -1
- package/dist/public/operations/types.d.ts +1 -1
- package/dist/public/operations/types.d.ts.map +1 -1
- package/dist/public/proof-receipt.d.ts +1 -0
- package/dist/public/proof-receipt.d.ts.map +1 -1
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +52 -0
- package/dist/public/schemas/relationship.d.ts.map +1 -1
- package/dist/public/schemas/relationship.js +2 -0
- package/dist/public/skills/kibi-bootstrap/SKILL.md +52 -11
- package/dist/public/skills/kibi-bootstrap/resources/bootstrap.md +12 -1
- package/dist/public/skills/kibi-usage/SKILL.md +82 -18
- package/dist/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
- package/dist/public/skills/kibi-usage/resources/kb-improvement.md +1 -1
- package/dist/public/skills/kibi-usage/resources/logic-ir.md +2 -2
- package/dist/public/skills/kibi-usage/resources/operation-access.md +29 -1
- package/dist/public/skills/kibi-usage/resources/ui-requirements.md +4 -4
- package/dist/public/skills/kibi-usage/resources/workflows.md +42 -25
- package/dist/public/telemetry-acceptance.d.ts +64 -0
- package/dist/public/telemetry-acceptance.d.ts.map +1 -1
- package/dist/public/telemetry-acceptance.js +143 -7
- package/dist/public/telemetry-remediation.d.ts.map +1 -1
- package/dist/public/telemetry-remediation.js +26 -10
- package/dist/relationships/shards.d.ts +13 -0
- package/dist/relationships/shards.d.ts.map +1 -1
- package/dist/relationships/shards.js +54 -28
- package/dist/runtime/cli-runtime.d.ts.map +1 -1
- package/dist/runtime/cli-runtime.js +44 -3
- package/dist/runtime/detached-snapshot.d.ts +38 -0
- package/dist/runtime/detached-snapshot.d.ts.map +1 -0
- package/dist/runtime/detached-snapshot.js +92 -0
- package/dist/schemas/entity.schema.json +122 -0
- package/dist/search-answer.d.ts +157 -0
- package/dist/search-answer.d.ts.map +1 -0
- package/dist/search-answer.js +846 -0
- package/dist/traceability/impact-knowledge.d.ts.map +1 -1
- package/dist/traceability/impact-knowledge.js +7 -0
- package/dist/traceability/symbol-extract.js +3 -3
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +12 -0
- package/dist/types/entities.d.ts +13 -0
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/types/relationships.d.ts +1 -1
- package/dist/types/relationships.d.ts.map +1 -1
- package/dist/utils/branch-resolver.d.ts +37 -1
- package/dist/utils/branch-resolver.d.ts.map +1 -1
- package/dist/utils/branch-resolver.js +130 -3
- package/dist/utils/repo-relative-path.d.ts +18 -0
- package/dist/utils/repo-relative-path.d.ts.map +1 -0
- package/dist/utils/repo-relative-path.js +58 -0
- package/dist/utils/rule-registry.generated.d.ts +75 -3
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +89 -3
- package/dist/utils/schema-version.d.ts +1 -1
- package/dist/utils/schema-version.d.ts.map +1 -1
- package/dist/utils/schema-version.js +4 -3
- package/dist/utils/strict-modeling.d.ts +1 -1
- package/dist/utils/strict-modeling.d.ts.map +1 -1
- package/dist/utils/strict-modeling.js +23 -3
- package/package.json +3 -3
- package/schema/entities.pl +22 -0
- package/schema/relationships.pl +14 -0
- package/schema/validation.pl +45 -1
- package/src/public/branch-resolver.ts +1 -0
- package/src/public/diagnostic-usage.ts +59 -1
- package/src/public/entity-origin.ts +175 -0
- package/src/public/impact/full-kb-quality.ts +1 -0
- package/src/public/impact/requirement-quality.ts +1 -1
- package/src/public/operations/catalog.ts +3 -0
- package/src/public/operations/check-executor.ts +15 -0
- package/src/public/operations/contracts.ts +75 -15
- package/src/public/operations/discovery-entities.ts +74 -0
- package/src/public/operations/discovery-executors.ts +88 -10
- package/src/public/operations/index.ts +1 -0
- package/src/public/operations/migration-plan.ts +30 -1
- package/src/public/operations/node-ports.ts +24 -3
- package/src/public/operations/origin-review.ts +375 -0
- package/src/public/operations/predicate-schema-conformance.ts +1 -1
- package/src/public/operations/repair-plan.ts +4 -0
- package/src/public/operations/result-envelope.ts +18 -6
- package/src/public/operations/runtime-types.ts +9 -0
- package/src/public/operations/schema6-check-actions.ts +699 -0
- package/src/public/operations/source-path-dangling.ts +81 -0
- package/src/public/operations/specs/bootstrap.ts +78 -4
- package/src/public/operations/specs/check-rules.generated.ts +15 -1
- package/src/public/operations/specs/check.ts +1 -1
- package/src/public/operations/specs/composite.ts +141 -0
- package/src/public/operations/specs/discovery.ts +9 -3
- package/src/public/operations/specs/modeling.ts +2 -2
- package/src/public/operations/specs/mutation-schemas.ts +49 -7
- package/src/public/operations/specs/mutation.ts +1 -1
- package/src/public/operations/specs/planning.ts +4 -4
- package/src/public/operations/specs/reporting.ts +13 -38
- package/src/public/operations/specs/upsert.ts +7 -1
- package/src/public/operations/symbol-repair-plan.ts +3 -1
- package/src/public/operations/types.ts +2 -0
- package/src/public/proof-receipt.ts +1 -0
- package/src/public/schemas/entity.ts +57 -0
- package/src/public/schemas/relationship.ts +2 -0
- package/src/public/skills/kibi-bootstrap/SKILL.md +52 -11
- package/src/public/skills/kibi-bootstrap/resources/bootstrap.md +12 -1
- package/src/public/skills/kibi-usage/SKILL.md +82 -18
- package/src/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
- package/src/public/skills/kibi-usage/resources/kb-improvement.md +1 -1
- package/src/public/skills/kibi-usage/resources/logic-ir.md +2 -2
- package/src/public/skills/kibi-usage/resources/operation-access.md +29 -1
- package/src/public/skills/kibi-usage/resources/ui-requirements.md +4 -4
- package/src/public/skills/kibi-usage/resources/workflows.md +42 -25
- package/src/public/telemetry-acceptance-types.d.ts +4 -1
- package/src/public/telemetry-acceptance.ts +210 -7
- package/src/public/telemetry-remediation.ts +39 -10
- package/src/schemas/entity.schema.json +122 -0
- package/src/schemas/relationship.schema.json +2 -0
|
@@ -9,15 +9,20 @@ import { executeStatus } from "../../public/operations/discovery-executors.js";
|
|
|
9
9
|
import { PREDICATE_SCHEMA_ALIGNMENT_CODE, migrationPlanHash, } from "../../public/operations/migration-plan.js";
|
|
10
10
|
import { readMigrationConfigStatus } from "../../public/operations/migration-plan.js";
|
|
11
11
|
import { readWorkspaceSnapshot } from "../../public/operations/workspace-snapshot.js";
|
|
12
|
+
import { computeShardPath, renderShardWithRelationship, } from "../../relationships/shards.js";
|
|
12
13
|
import { canonicalFilesystemPath } from "../../utils/canonical-path.js";
|
|
13
14
|
import { isDerivedKbPath } from "../../utils/kb-paths.js";
|
|
14
15
|
import { bootstrapEmptyKbSnapshotId, bootstrapPlanHash, } from "../bootstrap/types.js";
|
|
16
|
+
import { validateBootstrapPayload } from "../bootstrap/validation.js";
|
|
17
|
+
import { SCHEMA6_AUTOMATIC_CODES, applySchema6MigrationAction, } from "../migration/schema6.js";
|
|
18
|
+
import { buildUpsertBatchCommitGoal, formatUpsertError, } from "../mutation/contradictions.js";
|
|
15
19
|
import { executeDelete } from "../mutation/delete.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
20
|
+
import { PLAN_APPLY_JOURNAL_VERSION, contentHash, createPlanApplyJournal, isPlanApplyJournalId, openPlanApplyJournal, openPlanApplyJournalById, planRecoveryNotes, publishJournaledFiles, reconcilePendingSourceReceipts, recordPlanApplyJournal, recoverPlanApplyJournal, restoreJournaledFiles, settlePendingPlanApplyJournals, storeFingerprint, withPlanRecoveryNotes, } from "../mutation/plan-apply-journal.js";
|
|
21
|
+
import { canonicalSourcePath, renderSourceDocument, } from "../mutation/source-authoring.js";
|
|
22
|
+
import { assertRelationshipShardContained, executeUpsert, validateUpsertForCommit, } from "../mutation/upsert.js";
|
|
18
23
|
import { acquireWorkspaceMutationLock, releaseWorkspaceMutationLock, } from "../mutation/workspace-mutation-lock.js";
|
|
19
|
-
import { compilePlanHash, } from "./compile-intent.js";
|
|
20
|
-
// implements REQ-kibi-change-to-proof-plan-compiler
|
|
24
|
+
import { compilePlanHash, isBlockingWitness, parseWhatIfAnalysis, planWhatIfGoal, } from "./compile-intent.js";
|
|
25
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2
|
|
21
26
|
export const PLAN_APPLY_RESULT_VERSION = "kibi.plan-apply-result.v1";
|
|
22
27
|
const ENTITY_TYPES = new Set([
|
|
23
28
|
"req",
|
|
@@ -38,21 +43,6 @@ function isRecord(value) {
|
|
|
38
43
|
function digest(value) {
|
|
39
44
|
return createHash("sha256").update(value).digest("hex");
|
|
40
45
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Reconcile pending-source receipts with committed source writes: a written
|
|
43
|
-
* file is bound to its exact bytes until Git tracks it, and a deleted file
|
|
44
|
-
* must not leave a receipt that later syncs would report as missing.
|
|
45
|
-
*/
|
|
46
|
-
function reconcilePendingSourceReceipts(workspaceRoot, writes) {
|
|
47
|
-
for (const write of writes) {
|
|
48
|
-
if ((write.mode ?? "write") === "delete") {
|
|
49
|
-
retirePendingSourceReceipt(workspaceRoot, write.path);
|
|
50
|
-
}
|
|
51
|
-
else if (write.afterHash !== null) {
|
|
52
|
-
writePendingSourceReceipt(workspaceRoot, write.path, write.afterHash);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
46
|
function relationships(step) {
|
|
57
47
|
if (!Array.isArray(step.relationships))
|
|
58
48
|
return [];
|
|
@@ -204,6 +194,33 @@ function validateBootstrapPlanShape(args) {
|
|
|
204
194
|
throw new Error("Bootstrap apply failed: ready plans must contain actions");
|
|
205
195
|
return orderBootstrapActions(plan.actions);
|
|
206
196
|
}
|
|
197
|
+
// implements REQ-KIBI-BOOTSTRAP-PLAN
|
|
198
|
+
function preflightBootstrapActions(actions, context) {
|
|
199
|
+
for (const action of actions) {
|
|
200
|
+
try {
|
|
201
|
+
const input = validateBootstrapPayload(action.payload, context.clock());
|
|
202
|
+
if (input.document?.path !== undefined)
|
|
203
|
+
canonicalSourcePath(context, input, {
|
|
204
|
+
id: input.id,
|
|
205
|
+
...input.properties,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
throw new OperationError("BOOTSTRAP_PLAN_INVALID", `Bootstrap action ${action.id} is invalid: ${error instanceof Error ? error.message : String(error)}. Re-plan before applying; no bootstrap writes or journal were created.`, false);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// implements REQ-KIBI-BOOTSTRAP-PLAN
|
|
214
|
+
function isDeterministicBootstrapFailure(error) {
|
|
215
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
216
|
+
return (/(?:Entity|Relationship) validation failed|Proposition-complete ingestion failed|Contradiction detected for requirement|(?:'type' and 'id' are required|unsupported step entity type|every step needs an entity id)/i.test(detail) ||
|
|
217
|
+
(error instanceof OperationError &&
|
|
218
|
+
[
|
|
219
|
+
"BOOTSTRAP_PLAN_INVALID",
|
|
220
|
+
"SOURCE_PATH_INVALID",
|
|
221
|
+
"DOCUMENT_PATH_REQUIRED",
|
|
222
|
+
].includes(error.code)));
|
|
223
|
+
}
|
|
207
224
|
function isEntityDeletionApplyArgs(args) {
|
|
208
225
|
return "plan" in args && args.plan.version === "kibi.entity-deletion-plan.v1";
|
|
209
226
|
}
|
|
@@ -307,6 +324,48 @@ async function validateSources(context, sourceHashes) {
|
|
|
307
324
|
}
|
|
308
325
|
return checked;
|
|
309
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Validate one approved source write against the live workspace: the target
|
|
329
|
+
* must be workspace-relative, stay inside the workspace (also through
|
|
330
|
+
* symlinks), avoid Kibi's derived runtime trees, still hold the approved
|
|
331
|
+
* before-bytes, and carry an after-hash that matches its staged body.
|
|
332
|
+
*/
|
|
333
|
+
async function resolveSourceWriteTarget(workspaceRoot, fsPort, write) {
|
|
334
|
+
if (!write.path ||
|
|
335
|
+
path.isAbsolute(write.path) ||
|
|
336
|
+
write.path.split(/[\\/]/).includes("..")) {
|
|
337
|
+
throw new Error(`Apply plan failed: sourceWrites.path must be workspace-relative: ${write.path}`);
|
|
338
|
+
}
|
|
339
|
+
const absolute = path.resolve(workspaceRoot, write.path);
|
|
340
|
+
const root = path.resolve(workspaceRoot);
|
|
341
|
+
assertSourceWriteStaysInWorkspace(root, absolute, write.path);
|
|
342
|
+
const relative = path.relative(root, absolute).split(path.sep).join("/");
|
|
343
|
+
if (relative === ".kb" || isDerivedKbPath(relative)) {
|
|
344
|
+
throw new Error("Apply plan failed: sourceWrites.path cannot target Kibi's derived .kb runtime trees");
|
|
345
|
+
}
|
|
346
|
+
const realRoot = canonicalFilesystemPath(root);
|
|
347
|
+
const realExisting = canonicalFilesystemPath(absolute);
|
|
348
|
+
if (realExisting !== realRoot &&
|
|
349
|
+
!realExisting.startsWith(`${realRoot}${path.sep}`)) {
|
|
350
|
+
throw new Error(`Apply plan failed: sourceWrites.path follows a symlink outside the workspace: ${write.path}`);
|
|
351
|
+
}
|
|
352
|
+
const existing = await fsPort.readFile(absolute).catch(() => undefined);
|
|
353
|
+
const beforeHash = existing === undefined ? null : digest(existing);
|
|
354
|
+
if (beforeHash !== write.beforeHash) {
|
|
355
|
+
throw new Error(`Apply plan failed: source hash changed for ${write.path}`);
|
|
356
|
+
}
|
|
357
|
+
const mode = write.mode ?? "write";
|
|
358
|
+
if (mode === "write" &&
|
|
359
|
+
(write.body === undefined ||
|
|
360
|
+
write.afterHash === null ||
|
|
361
|
+
digest(write.body) !== write.afterHash)) {
|
|
362
|
+
throw new Error(`Apply plan failed: afterHash does not match staged body for ${write.path}`);
|
|
363
|
+
}
|
|
364
|
+
if (mode === "delete" && write.afterHash !== null) {
|
|
365
|
+
throw new Error(`Apply plan failed: delete source write must have a null afterHash for ${write.path}`);
|
|
366
|
+
}
|
|
367
|
+
return { absolute, relative, existing, mode };
|
|
368
|
+
}
|
|
310
369
|
async function applySourceWrites(context, writes, planHash, allowReplay = false, onCommitted = () => undefined) {
|
|
311
370
|
if (writes.length === 0) {
|
|
312
371
|
return { paths: [], rollback: async () => undefined, journalId: null };
|
|
@@ -412,44 +471,7 @@ async function applySourceWrites(context, writes, planHash, allowReplay = false,
|
|
|
412
471
|
try {
|
|
413
472
|
// Validate every target and hash before touching the working tree.
|
|
414
473
|
for (const write of writes) {
|
|
415
|
-
|
|
416
|
-
path.isAbsolute(write.path) ||
|
|
417
|
-
write.path.split(/[\\/]/).includes("..")) {
|
|
418
|
-
throw new Error(`Apply plan failed: sourceWrites.path must be workspace-relative: ${write.path}`);
|
|
419
|
-
}
|
|
420
|
-
const absolute = path.resolve(context.workspaceRoot, write.path);
|
|
421
|
-
const root = path.resolve(context.workspaceRoot);
|
|
422
|
-
assertSourceWriteStaysInWorkspace(root, absolute, write.path);
|
|
423
|
-
const workspaceRelative = path
|
|
424
|
-
.relative(root, absolute)
|
|
425
|
-
.split(path.sep)
|
|
426
|
-
.join("/");
|
|
427
|
-
if (workspaceRelative === ".kb" || isDerivedKbPath(workspaceRelative)) {
|
|
428
|
-
throw new Error("Apply plan failed: sourceWrites.path cannot target Kibi's derived .kb runtime trees");
|
|
429
|
-
}
|
|
430
|
-
const realRoot = canonicalFilesystemPath(root);
|
|
431
|
-
const realExisting = canonicalFilesystemPath(absolute);
|
|
432
|
-
if (realExisting !== realRoot &&
|
|
433
|
-
!realExisting.startsWith(`${realRoot}${path.sep}`)) {
|
|
434
|
-
throw new Error(`Apply plan failed: sourceWrites.path follows a symlink outside the workspace: ${write.path}`);
|
|
435
|
-
}
|
|
436
|
-
const existing = await context.fs
|
|
437
|
-
.readFile(absolute)
|
|
438
|
-
.catch(() => undefined);
|
|
439
|
-
const beforeHash = existing === undefined ? null : digest(existing);
|
|
440
|
-
if (beforeHash !== write.beforeHash) {
|
|
441
|
-
throw new Error(`Apply plan failed: source hash changed for ${write.path}`);
|
|
442
|
-
}
|
|
443
|
-
const mode = write.mode ?? "write";
|
|
444
|
-
if (mode === "write" &&
|
|
445
|
-
(write.body === undefined ||
|
|
446
|
-
write.afterHash === null ||
|
|
447
|
-
digest(write.body) !== write.afterHash)) {
|
|
448
|
-
throw new Error(`Apply plan failed: afterHash does not match staged body for ${write.path}`);
|
|
449
|
-
}
|
|
450
|
-
if (mode === "delete" && write.afterHash !== null) {
|
|
451
|
-
throw new Error(`Apply plan failed: delete source write must have a null afterHash for ${write.path}`);
|
|
452
|
-
}
|
|
474
|
+
const { absolute, existing, mode } = await resolveSourceWriteTarget(context.workspaceRoot, fsPort, write);
|
|
453
475
|
originals.push({ absolute, body: existing });
|
|
454
476
|
paths.push(write.path);
|
|
455
477
|
const stageBase = path.join(context.workspaceRoot, ".kb", "recovery", `${journalId}-${entries.length}`);
|
|
@@ -656,6 +678,7 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
656
678
|
const actions = recovery
|
|
657
679
|
? [...(remainingActions ?? args.plan.actions)]
|
|
658
680
|
: validateBootstrapPlanShape(args);
|
|
681
|
+
preflightBootstrapActions(actions, context);
|
|
659
682
|
const prolog = context.prolog ?? (await context.ensureProlog?.());
|
|
660
683
|
if (!prolog)
|
|
661
684
|
throw new Error("Bootstrap apply requires a Prolog runtime");
|
|
@@ -828,19 +851,22 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
828
851
|
}
|
|
829
852
|
catch (error) {
|
|
830
853
|
const detail = error instanceof Error ? error.message : String(error);
|
|
854
|
+
const deterministic = isDeterministicBootstrapFailure(error);
|
|
831
855
|
results.push({ actionId: action.id, outcome: "failed", detail });
|
|
832
|
-
failures.push({ actionId: action.id, detail });
|
|
833
|
-
await writeJournal("repair_required", activeActionId, lastCheckpoint);
|
|
856
|
+
failures.push({ actionId: action.id, detail, deterministic });
|
|
857
|
+
await writeJournal(deterministic ? "rejected" : "repair_required", activeActionId, lastCheckpoint);
|
|
834
858
|
return {
|
|
835
859
|
content: [
|
|
836
860
|
{
|
|
837
861
|
type: "text",
|
|
838
|
-
text:
|
|
862
|
+
text: deterministic
|
|
863
|
+
? `Bootstrap plan rejected at ${action.id}: ${detail}. ${results.filter((row) => row.outcome === "applied").length} action(s) committed; re-plan from the current state.`
|
|
864
|
+
: `Bootstrap plan ${args.plan.planHash.slice(0, 12)} partially applied; repair journal ${journalId}.`,
|
|
839
865
|
},
|
|
840
866
|
],
|
|
841
867
|
structuredContent: {
|
|
842
868
|
version: PLAN_APPLY_RESULT_VERSION,
|
|
843
|
-
outcome: "partially_applied",
|
|
869
|
+
outcome: deterministic ? "rejected" : "partially_applied",
|
|
844
870
|
planHash: args.plan.planHash,
|
|
845
871
|
actionResults: results,
|
|
846
872
|
changedEntities,
|
|
@@ -859,16 +885,24 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
859
885
|
sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes)
|
|
860
886
|
.length,
|
|
861
887
|
notes: [
|
|
862
|
-
|
|
888
|
+
deterministic
|
|
889
|
+
? "Bootstrap stopped at a deterministic action failure; the journal is terminal. Re-plan from the current state."
|
|
890
|
+
: "Bootstrap application stopped at a repairable action failure.",
|
|
863
891
|
],
|
|
864
892
|
},
|
|
865
|
-
status: "committed_with_repairs",
|
|
893
|
+
status: deterministic ? "rejected" : "committed_with_repairs",
|
|
866
894
|
effectFailures: failures,
|
|
867
895
|
nextActions: [
|
|
868
896
|
{
|
|
869
|
-
operation:
|
|
870
|
-
|
|
871
|
-
|
|
897
|
+
operation: deterministic
|
|
898
|
+
? "kb_plan_bootstrap"
|
|
899
|
+
: "kb_apply_plan",
|
|
900
|
+
...(deterministic
|
|
901
|
+
? {}
|
|
902
|
+
: { input: { recoveryJournalId: journalId } }),
|
|
903
|
+
reason: deterministic
|
|
904
|
+
? "Re-plan a corrected bootstrap from the current state; this journal cannot be replayed."
|
|
905
|
+
: "Resume the remaining bootstrap actions from the immutable recovery journal; do not retry the original plan.",
|
|
872
906
|
required: true,
|
|
873
907
|
},
|
|
874
908
|
],
|
|
@@ -963,9 +997,29 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
|
|
|
963
997
|
},
|
|
964
998
|
};
|
|
965
999
|
}
|
|
966
|
-
// implements REQ-kibi-change-to-proof-plan-compiler, REQ-
|
|
1000
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-core-atomic-upsert-persistence
|
|
967
1001
|
async function executeApplyPlanUnlocked(args, context, onCommitted = () => undefined) {
|
|
1002
|
+
// Finish any interrupted plan application before this call reads or writes
|
|
1003
|
+
// anything, and report what was done alongside this call's own result.
|
|
1004
|
+
const recovered = await recoverInterruptedPlans(context, "recoveryJournalId" in args ? args.recoveryJournalId : undefined);
|
|
1005
|
+
if (recovered.some((recovery) => recovery.action !== "none"))
|
|
1006
|
+
onCommitted();
|
|
1007
|
+
let result;
|
|
1008
|
+
try {
|
|
1009
|
+
result = await dispatchApplyPlan(args, context, onCommitted, recovered);
|
|
1010
|
+
}
|
|
1011
|
+
catch (error) {
|
|
1012
|
+
// A plan compiled before the recovery commonly fails its snapshot check
|
|
1013
|
+
// now; the failure still names the interrupted plan that was settled.
|
|
1014
|
+
throw withPlanRecoveryNotes(error, planRecoveryNotes(recovered));
|
|
1015
|
+
}
|
|
1016
|
+
return withRecoveryReport(result, recovered);
|
|
1017
|
+
}
|
|
1018
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-agent-guided-migration-orchestration
|
|
1019
|
+
async function dispatchApplyPlan(args, context, onCommitted, recovered) {
|
|
968
1020
|
if ("recoveryJournalId" in args) {
|
|
1021
|
+
if (isPlanApplyJournalId(args.recoveryJournalId))
|
|
1022
|
+
return executePlanJournalRecovery(args.recoveryJournalId, context, recovered);
|
|
969
1023
|
if (args.recoveryJournalId.startsWith("bootstrap-")) {
|
|
970
1024
|
if (!context.fs)
|
|
971
1025
|
throw new Error("Bootstrap recovery requires a filesystem-capable runtime");
|
|
@@ -977,6 +1031,10 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
|
|
|
977
1031
|
journal.kind !== "bootstrap" ||
|
|
978
1032
|
!journal.plan)
|
|
979
1033
|
throw new Error("Bootstrap recovery journal is invalid");
|
|
1034
|
+
if (journal.state === "rejected" ||
|
|
1035
|
+
(journal.results ?? []).some((row) => row.outcome === "failed" &&
|
|
1036
|
+
isDeterministicBootstrapFailure(new Error(String(row.detail)))))
|
|
1037
|
+
throw new OperationError("BOOTSTRAP_PLAN_REJECTED", "Bootstrap recovery refused: deterministic failure is terminal; re-plan from the current state.", false);
|
|
980
1038
|
if (!/^[a-f0-9]{64}$/i.test(journal.plan.planHash) ||
|
|
981
1039
|
bootstrapPlanHash(journal.plan) !== journal.plan.planHash)
|
|
982
1040
|
throw new Error("Bootstrap recovery journal plan hash is invalid");
|
|
@@ -1098,7 +1156,428 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
|
|
|
1098
1156
|
},
|
|
1099
1157
|
};
|
|
1100
1158
|
}
|
|
1159
|
+
return executeCompilePlan(args, context, onCommitted, recovered);
|
|
1160
|
+
}
|
|
1161
|
+
// implements REQ-kibi-truthful-consistency
|
|
1162
|
+
/**
|
|
1163
|
+
* Run every step through the validation chain executeUpsert runs. Each step
|
|
1164
|
+
* is validated against the live store plus what the earlier steps will have
|
|
1165
|
+
* written by then (`staged`): entities they create count as relationship
|
|
1166
|
+
* targets, their kinds and claim keys are read from the plan, their
|
|
1167
|
+
* relationships merge into a later upsert of the same entity, and their
|
|
1168
|
+
* predicate schemas govern later predicate facts.
|
|
1169
|
+
*/
|
|
1170
|
+
async function validatePlanSteps(steps, context) {
|
|
1171
|
+
const stagedEntities = new Map();
|
|
1172
|
+
const stagedRelationships = [];
|
|
1173
|
+
const validated = [];
|
|
1174
|
+
for (const step of steps) {
|
|
1175
|
+
try {
|
|
1176
|
+
const { validated: result } = await validateUpsertForCommit(step, context, {
|
|
1177
|
+
staged: {
|
|
1178
|
+
entities: stagedEntities,
|
|
1179
|
+
relationships: [...stagedRelationships],
|
|
1180
|
+
},
|
|
1181
|
+
});
|
|
1182
|
+
stagedEntities.set(step.id, result.entity);
|
|
1183
|
+
stagedRelationships.push(...result.relationships);
|
|
1184
|
+
validated.push({
|
|
1185
|
+
input: step,
|
|
1186
|
+
entity: result.entity,
|
|
1187
|
+
relationships: result.relationships,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
throw new Error(`step ${step.id} is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
return validated;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Every workspace file the plan changes, with exact before/after bytes: the
|
|
1198
|
+
* plan's approved sourceWrites, the authored document of every entity step,
|
|
1199
|
+
* and the relationship shards its steps append, each rendered exactly as
|
|
1200
|
+
* executeUpsert would write it, in step order. `sources` maps a step index to
|
|
1201
|
+
* its document, which becomes the committed entity's `source`.
|
|
1202
|
+
*/
|
|
1203
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2
|
|
1204
|
+
async function planFileWrites(context, prolog, fsPort, sourceWrites, steps, now) {
|
|
1205
|
+
const workspaceRoot = context.workspaceRoot;
|
|
1206
|
+
const files = new Map();
|
|
1207
|
+
for (const write of sourceWrites) {
|
|
1208
|
+
const target = await resolveSourceWriteTarget(workspaceRoot, fsPort, write);
|
|
1209
|
+
if (files.has(target.relative)) {
|
|
1210
|
+
throw new Error(`Apply plan failed: sourceWrites lists ${write.path} more than once`);
|
|
1211
|
+
}
|
|
1212
|
+
if (target.mode === "delete" && !fsPort.unlink) {
|
|
1213
|
+
throw new Error(`Apply plan failed: delete requires filesystem unlink support: ${write.path}`);
|
|
1214
|
+
}
|
|
1215
|
+
const before = target.existing ?? null;
|
|
1216
|
+
const after = target.mode === "write" ? (write.body ?? "") : null;
|
|
1217
|
+
files.set(target.relative, {
|
|
1218
|
+
path: target.relative,
|
|
1219
|
+
origin: "plan",
|
|
1220
|
+
mode: target.mode,
|
|
1221
|
+
before,
|
|
1222
|
+
beforeHash: before === null ? null : contentHash(before),
|
|
1223
|
+
after,
|
|
1224
|
+
afterHash: after === null ? null : contentHash(after),
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
// Entity documents: the bytes kb_upsert would publish for the validated
|
|
1228
|
+
// entity, merged into the document as the plan leaves it. A plan without
|
|
1229
|
+
// them leaves its entities only in the branch store, so a rebuild or a
|
|
1230
|
+
// fresh clone loses them. Symbols keep the kb_upsert path, which also
|
|
1231
|
+
// refreshes their generated coordinates.
|
|
1232
|
+
const sources = new Map();
|
|
1233
|
+
for (const [index, step] of steps.entries()) {
|
|
1234
|
+
if (step.input.type === "symbol")
|
|
1235
|
+
continue;
|
|
1236
|
+
const [existing] = step.input.document?.path === undefined
|
|
1237
|
+
? await loadEntities(prolog, {
|
|
1238
|
+
id: step.input.id,
|
|
1239
|
+
type: step.input.type,
|
|
1240
|
+
})
|
|
1241
|
+
: [];
|
|
1242
|
+
const relative = canonicalSourcePath(context, step.input, step.entity, existing);
|
|
1243
|
+
let file = files.get(relative);
|
|
1244
|
+
if (file === undefined) {
|
|
1245
|
+
// canonicalSourcePath already refused traversal, derived .kb trees and
|
|
1246
|
+
// symlinks that leave the workspace.
|
|
1247
|
+
const existingBytes = await fsPort
|
|
1248
|
+
.readFile(path.join(workspaceRoot, relative))
|
|
1249
|
+
.catch(() => undefined);
|
|
1250
|
+
const before = existingBytes ?? null;
|
|
1251
|
+
file = {
|
|
1252
|
+
path: relative,
|
|
1253
|
+
origin: "entity-document",
|
|
1254
|
+
mode: "write",
|
|
1255
|
+
before,
|
|
1256
|
+
beforeHash: before === null ? null : contentHash(before),
|
|
1257
|
+
after: before,
|
|
1258
|
+
afterHash: before === null ? null : contentHash(before),
|
|
1259
|
+
};
|
|
1260
|
+
files.set(relative, file);
|
|
1261
|
+
}
|
|
1262
|
+
if (file.mode === "delete") {
|
|
1263
|
+
throw new Error(`Apply plan failed: step ${step.input.id} writes ${relative}, which the plan deletes`);
|
|
1264
|
+
}
|
|
1265
|
+
sources.set(index, relative);
|
|
1266
|
+
// A plan sourceWrite for the same file is approved, byte-exact content:
|
|
1267
|
+
// it is the document, not a base to render into.
|
|
1268
|
+
if (file.origin === "plan")
|
|
1269
|
+
continue;
|
|
1270
|
+
const after = renderSourceDocument(step.input, step.entity, file.after ?? undefined, relative);
|
|
1271
|
+
file.after = after;
|
|
1272
|
+
file.afterHash = contentHash(after);
|
|
1273
|
+
}
|
|
1274
|
+
const root = path.resolve(workspaceRoot);
|
|
1275
|
+
const kbRoot = path.join(workspaceRoot, ".kb");
|
|
1276
|
+
const createdAt = now.toISOString();
|
|
1277
|
+
for (const step of steps) {
|
|
1278
|
+
for (const relationship of step.relationships) {
|
|
1279
|
+
const type = typeof relationship.type === "string" ? relationship.type : "";
|
|
1280
|
+
const from = typeof relationship.from === "string"
|
|
1281
|
+
? relationship.from
|
|
1282
|
+
: step.input.id;
|
|
1283
|
+
const to = typeof relationship.to === "string" ? relationship.to : "";
|
|
1284
|
+
if (!type || !from || !to)
|
|
1285
|
+
continue;
|
|
1286
|
+
const shardPath = computeShardPath(kbRoot, from);
|
|
1287
|
+
assertRelationshipShardContained(workspaceRoot, shardPath);
|
|
1288
|
+
const relative = path
|
|
1289
|
+
.relative(root, path.resolve(shardPath))
|
|
1290
|
+
.split(path.sep)
|
|
1291
|
+
.join("/");
|
|
1292
|
+
let file = files.get(relative);
|
|
1293
|
+
if (file === undefined) {
|
|
1294
|
+
const existing = await fsPort.readFile(shardPath).catch(() => null);
|
|
1295
|
+
const existingHash = existing === null ? null : contentHash(existing);
|
|
1296
|
+
file = {
|
|
1297
|
+
path: relative,
|
|
1298
|
+
origin: "relationship-shard",
|
|
1299
|
+
mode: "write",
|
|
1300
|
+
before: existing,
|
|
1301
|
+
beforeHash: existingHash,
|
|
1302
|
+
after: existing,
|
|
1303
|
+
afterHash: existingHash,
|
|
1304
|
+
};
|
|
1305
|
+
files.set(relative, file);
|
|
1306
|
+
}
|
|
1307
|
+
const next = renderShardWithRelationship(shardPath, file.after, {
|
|
1308
|
+
type,
|
|
1309
|
+
from,
|
|
1310
|
+
to,
|
|
1311
|
+
created_at: createdAt,
|
|
1312
|
+
created_by: "kibi/upsert",
|
|
1313
|
+
source: "mcp://kibi/upsert",
|
|
1314
|
+
});
|
|
1315
|
+
if (next !== null) {
|
|
1316
|
+
file.mode = "write";
|
|
1317
|
+
file.after = next;
|
|
1318
|
+
file.afterHash = contentHash(next);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return {
|
|
1323
|
+
files: [...files.values()].filter((file) => file.origin === "plan" ||
|
|
1324
|
+
file.origin === "entity-document" ||
|
|
1325
|
+
file.afterHash !== file.beforeHash),
|
|
1326
|
+
sources,
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
function errorMessage(error) {
|
|
1330
|
+
return error instanceof Error ? error.message : String(error);
|
|
1331
|
+
}
|
|
1332
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-core-atomic-upsert-persistence
|
|
1333
|
+
/**
|
|
1334
|
+
* Apply a validated compile plan all-or-nothing:
|
|
1335
|
+
* 1. journal every file change (exact bytes) and every store upsert, plus a
|
|
1336
|
+
* fingerprint of the touched store entities, before the first write;
|
|
1337
|
+
* 2. publish the files with temp-file + rename (fsync where available);
|
|
1338
|
+
* 3. commit every step in one Prolog batch transaction — the only commit
|
|
1339
|
+
* point.
|
|
1340
|
+
* Any failure before the commit restores every file from the journal and
|
|
1341
|
+
* leaves the store untouched; an interrupted application is completed or
|
|
1342
|
+
* rolled back from the journal by the next mutating call.
|
|
1343
|
+
*/
|
|
1344
|
+
async function commitCompilePlan(context, prolog, plan, steps, revalidate, now, onCommitted) {
|
|
1345
|
+
// Store upserts, one per step. A step with an authored document commits
|
|
1346
|
+
// that document as its source, exactly as a source-first kb_upsert does.
|
|
1347
|
+
const storeEntries = (sources = new Map()) => steps.map((step, index) => {
|
|
1348
|
+
const source = sources.get(index);
|
|
1349
|
+
return {
|
|
1350
|
+
entity: source === undefined ? step.entity : { ...step.entity, source },
|
|
1351
|
+
relationships: step.relationships,
|
|
1352
|
+
skipContradictionCheck: step.input._skipContradictionCheck === true,
|
|
1353
|
+
};
|
|
1354
|
+
});
|
|
1355
|
+
let entries = storeEntries();
|
|
1356
|
+
const entityIds = [
|
|
1357
|
+
...new Set(entries.map((entry) => String(entry.entity.id))),
|
|
1358
|
+
];
|
|
1359
|
+
const changedRelationships = entries.reduce((sum, entry) => sum + entry.relationships.length, 0);
|
|
1360
|
+
const storeRejection = (result) => new Error(`the store rejected the plan's batch transaction: ${formatUpsertError(entityIds.length === 1
|
|
1361
|
+
? String(entityIds[0])
|
|
1362
|
+
: `plan batch [${entityIds.join(", ")}]`, result.error, result.errorRecord)}`);
|
|
1363
|
+
const fsPort = context.fs;
|
|
1364
|
+
if (fsPort === undefined) {
|
|
1365
|
+
if (plan.sourceWrites.length > 0) {
|
|
1366
|
+
throw new Error("Apply plan failed: sourceWrites require a filesystem-capable runtime");
|
|
1367
|
+
}
|
|
1368
|
+
// Without a filesystem there are no workspace files to coordinate: the
|
|
1369
|
+
// single store transaction is the whole application.
|
|
1370
|
+
const written = await prolog.query(buildUpsertBatchCommitGoal(entries));
|
|
1371
|
+
if (!written.success) {
|
|
1372
|
+
throw new Error(`Apply plan failed; no change was applied: ${storeRejection(written).message}`);
|
|
1373
|
+
}
|
|
1374
|
+
onCommitted();
|
|
1375
|
+
prolog.invalidateCache?.();
|
|
1376
|
+
return {
|
|
1377
|
+
journalId: null,
|
|
1378
|
+
changedEntities: entries.length,
|
|
1379
|
+
changedRelationships,
|
|
1380
|
+
documentPaths: [],
|
|
1381
|
+
effectFailures: [],
|
|
1382
|
+
nextActions: [],
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
const slot = openPlanApplyJournal(context, plan.planHash);
|
|
1386
|
+
if (slot.existing?.state === "committed") {
|
|
1387
|
+
throw new OperationError("MUTATION_ALREADY_COMMITTED", `MUTATION_ALREADY_COMMITTED: plan ${plan.planHash} was already applied (journal ${slot.journalId}); compile a fresh plan instead of applying it again`, false);
|
|
1388
|
+
}
|
|
1389
|
+
if (slot.existing !== null && slot.existing.state !== "rolled_back") {
|
|
1390
|
+
throw new OperationError("PLAN_APPLY_RECOVERY_REQUIRED", `Plan ${plan.planHash} has an unfinished journal ${slot.journalId}; run kb_apply_plan with recoveryJournalId=${slot.journalId} before applying it again`, false);
|
|
1391
|
+
}
|
|
1392
|
+
const { files, sources } = await planFileWrites(context, prolog, fsPort, plan.sourceWrites, steps, now);
|
|
1393
|
+
entries = storeEntries(sources);
|
|
1394
|
+
const goal = buildUpsertBatchCommitGoal(entries);
|
|
1395
|
+
const documentPaths = [
|
|
1396
|
+
...new Set([
|
|
1397
|
+
...plan.sourceWrites.map((write) => write.path),
|
|
1398
|
+
...sources.values(),
|
|
1399
|
+
]),
|
|
1400
|
+
];
|
|
1401
|
+
let preCommitFingerprint;
|
|
1402
|
+
try {
|
|
1403
|
+
preCommitFingerprint = await storeFingerprint(prolog, entityIds);
|
|
1404
|
+
}
|
|
1405
|
+
catch (error) {
|
|
1406
|
+
throw new Error(`Apply plan failed before any write: the store could not be fingerprinted for the plan journal: ${errorMessage(error)}`);
|
|
1407
|
+
}
|
|
1408
|
+
const handle = createPlanApplyJournal(slot.journalPath, {
|
|
1409
|
+
version: PLAN_APPLY_JOURNAL_VERSION,
|
|
1410
|
+
journalId: slot.journalId,
|
|
1411
|
+
planHash: plan.planHash,
|
|
1412
|
+
branch: slot.branch,
|
|
1413
|
+
state: "prepared",
|
|
1414
|
+
createdAt: now.toISOString(),
|
|
1415
|
+
updatedAt: now.toISOString(),
|
|
1416
|
+
files,
|
|
1417
|
+
store: { entityIds, preCommitFingerprint, entries },
|
|
1418
|
+
summary: {
|
|
1419
|
+
planPaths: plan.sourceWrites.map((write) => write.path),
|
|
1420
|
+
filePaths: files.map((file) => file.path),
|
|
1421
|
+
changedEntities: entries.length,
|
|
1422
|
+
changedRelationships,
|
|
1423
|
+
},
|
|
1424
|
+
});
|
|
1425
|
+
const rollBack = async (error, phase) => {
|
|
1426
|
+
let restored;
|
|
1427
|
+
try {
|
|
1428
|
+
restored = await restoreJournaledFiles(fsPort, context.workspaceRoot, handle.journalPath, files);
|
|
1429
|
+
}
|
|
1430
|
+
catch (rollbackError) {
|
|
1431
|
+
throw new OperationError("PLAN_APPLY_RECOVERY_REQUIRED", `Plan ${plan.planHash} failed ${phase} (${errorMessage(error)}), and restoring its source writes also failed (${errorMessage(rollbackError)}). Journal ${slot.journalId} is kept: the next kb_apply_plan or kb_upsert call rolls it back, or run kb_apply_plan with recoveryJournalId=${slot.journalId}`, false);
|
|
1432
|
+
}
|
|
1433
|
+
const detail = `Plan ${plan.planHash.slice(0, 12)} failed ${phase}; no change was applied: the store is unchanged and ${restored.length} source file(s) were restored from journal ${slot.journalId}.`;
|
|
1434
|
+
try {
|
|
1435
|
+
recordPlanApplyJournal(handle, {
|
|
1436
|
+
state: "rolled_back",
|
|
1437
|
+
resolution: { action: "rolled_back", by: "apply", detail },
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
catch {
|
|
1441
|
+
// The files are restored and the store never committed, so a journal
|
|
1442
|
+
// left in an earlier state resolves to the same rollback next time.
|
|
1443
|
+
}
|
|
1444
|
+
const message = `Apply plan failed ${phase}; no change was applied (store unchanged, ${restored.length} source file(s) restored from journal ${slot.journalId}): ${errorMessage(error)}`;
|
|
1445
|
+
if (error instanceof OperationError) {
|
|
1446
|
+
throw new OperationError(error.code, message, error.retryable);
|
|
1447
|
+
}
|
|
1448
|
+
throw new Error(message);
|
|
1449
|
+
};
|
|
1450
|
+
try {
|
|
1451
|
+
await publishJournaledFiles(fsPort, context.workspaceRoot, files);
|
|
1452
|
+
}
|
|
1453
|
+
catch (error) {
|
|
1454
|
+
return rollBack(error, "while publishing its source writes");
|
|
1455
|
+
}
|
|
1456
|
+
if (plan.sourceWrites.length > 0) {
|
|
1457
|
+
// Steps were validated against the workspace as it was; validate them
|
|
1458
|
+
// again against the published source bytes before the commit.
|
|
1459
|
+
try {
|
|
1460
|
+
await revalidate();
|
|
1461
|
+
}
|
|
1462
|
+
catch (error) {
|
|
1463
|
+
return rollBack(error, "validation against its published source writes");
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
try {
|
|
1467
|
+
recordPlanApplyJournal(handle, { state: "store_committing" });
|
|
1468
|
+
}
|
|
1469
|
+
catch (error) {
|
|
1470
|
+
return rollBack(error, "while journaling its store commit");
|
|
1471
|
+
}
|
|
1472
|
+
let written;
|
|
1473
|
+
let commitFailure;
|
|
1474
|
+
try {
|
|
1475
|
+
written = await prolog.query(goal);
|
|
1476
|
+
if (!written.success)
|
|
1477
|
+
commitFailure = storeRejection(written);
|
|
1478
|
+
}
|
|
1479
|
+
catch (error) {
|
|
1480
|
+
commitFailure = error;
|
|
1481
|
+
}
|
|
1482
|
+
const effectFailures = [];
|
|
1483
|
+
const nextActions = [];
|
|
1484
|
+
if (commitFailure !== undefined) {
|
|
1485
|
+
// The batch is one transaction: it either changed the plan's entities or
|
|
1486
|
+
// changed nothing. Decide from the store, not from the transport.
|
|
1487
|
+
let fingerprint;
|
|
1488
|
+
try {
|
|
1489
|
+
fingerprint = await storeFingerprint(prolog, entityIds);
|
|
1490
|
+
}
|
|
1491
|
+
catch (probeError) {
|
|
1492
|
+
if (written === undefined) {
|
|
1493
|
+
throw new OperationError("PLAN_APPLY_RECOVERY_REQUIRED", `Plan ${plan.planHash} lost contact with the store during its commit (${errorMessage(commitFailure)}), and the store could not be inspected (${errorMessage(probeError)}). Journal ${slot.journalId} is kept: the next kb_apply_plan or kb_upsert call completes or rolls it back, or run kb_apply_plan with recoveryJournalId=${slot.journalId}`, false);
|
|
1494
|
+
}
|
|
1495
|
+
// The store answered with a rejection; trust it.
|
|
1496
|
+
}
|
|
1497
|
+
if (fingerprint === undefined || fingerprint === preCommitFingerprint) {
|
|
1498
|
+
return rollBack(commitFailure, "at its store commit");
|
|
1499
|
+
}
|
|
1500
|
+
effectFailures.push({
|
|
1501
|
+
kind: "store-commit",
|
|
1502
|
+
errorCode: "STORE_COMMIT_REPORTED_FAILURE",
|
|
1503
|
+
detail: `The store reported a failure (${errorMessage(commitFailure)}) but shows the plan's batch committed; the plan is kept as applied.`,
|
|
1504
|
+
});
|
|
1505
|
+
nextActions.push({
|
|
1506
|
+
operation: "kb_status",
|
|
1507
|
+
reason: "The store committed the plan's batch while reporting a failure; confirm the branch snapshot is persisted before further writes.",
|
|
1508
|
+
required: true,
|
|
1509
|
+
}, {
|
|
1510
|
+
operation: "kb_check",
|
|
1511
|
+
reason: "Run the consistency checks after the unconfirmed store commit and follow their typed repair actions.",
|
|
1512
|
+
required: true,
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
// The batch transaction committed: every step is in the store.
|
|
1516
|
+
onCommitted();
|
|
1517
|
+
prolog.invalidateCache?.();
|
|
1518
|
+
try {
|
|
1519
|
+
recordPlanApplyJournal(handle, { state: "store_committed" });
|
|
1520
|
+
}
|
|
1521
|
+
catch {
|
|
1522
|
+
// A journal left at store_committing is completed on the next call: the
|
|
1523
|
+
// store fingerprint now shows the commit.
|
|
1524
|
+
}
|
|
1525
|
+
try {
|
|
1526
|
+
reconcilePendingSourceReceipts(context.workspaceRoot, files);
|
|
1527
|
+
}
|
|
1528
|
+
catch (error) {
|
|
1529
|
+
effectFailures.push({
|
|
1530
|
+
kind: "pending-source-receipt",
|
|
1531
|
+
errorCode: "PENDING_SOURCE_RECEIPT_FAILED",
|
|
1532
|
+
detail: errorMessage(error),
|
|
1533
|
+
});
|
|
1534
|
+
nextActions.push({
|
|
1535
|
+
operation: "kb_apply_plan",
|
|
1536
|
+
input: { recoveryJournalId: slot.journalId },
|
|
1537
|
+
reason: "The plan committed, but binding its new source files to pending-source receipts failed; recovering the journal finishes the receipts. Do not apply the original plan again.",
|
|
1538
|
+
required: true,
|
|
1539
|
+
});
|
|
1540
|
+
return {
|
|
1541
|
+
journalId: slot.journalId,
|
|
1542
|
+
changedEntities: entries.length,
|
|
1543
|
+
changedRelationships,
|
|
1544
|
+
documentPaths,
|
|
1545
|
+
effectFailures,
|
|
1546
|
+
nextActions,
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
try {
|
|
1550
|
+
recordPlanApplyJournal(handle, {
|
|
1551
|
+
state: "committed",
|
|
1552
|
+
resolution: {
|
|
1553
|
+
action: "completed",
|
|
1554
|
+
by: "apply",
|
|
1555
|
+
detail: `Applied plan ${plan.planHash.slice(0, 12)}: ${entries.length} step(s) in one store transaction and ${files.length} journaled file(s).`,
|
|
1556
|
+
},
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
catch {
|
|
1560
|
+
// A store_committed journal completes idempotently on the next call.
|
|
1561
|
+
}
|
|
1562
|
+
return {
|
|
1563
|
+
journalId: slot.journalId,
|
|
1564
|
+
changedEntities: entries.length,
|
|
1565
|
+
changedRelationships,
|
|
1566
|
+
documentPaths,
|
|
1567
|
+
effectFailures,
|
|
1568
|
+
nextActions,
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2
|
|
1572
|
+
async function executeCompilePlan(args, context, onCommitted, recovered) {
|
|
1101
1573
|
validateCompilePlanShape(args);
|
|
1574
|
+
// Re-applying a plan whose interrupted application this call just
|
|
1575
|
+
// completed returns that completion instead of failing its snapshot check.
|
|
1576
|
+
const completed = recovered.find((recovery) => recovery.planHash === args.plan.planHash &&
|
|
1577
|
+
recovery.action === "completed");
|
|
1578
|
+
if (completed !== undefined) {
|
|
1579
|
+
return planJournalRecoveryResult(context, completed, openPlanApplyJournalById(context, completed.journalId).journal);
|
|
1580
|
+
}
|
|
1102
1581
|
const prolog = context.prolog ?? (await context.ensureProlog?.()) ?? undefined;
|
|
1103
1582
|
if (!prolog)
|
|
1104
1583
|
throw new Error("Apply plan requires a Prolog runtime");
|
|
@@ -1106,10 +1585,16 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
|
|
|
1106
1585
|
// compiled entity steps against the staged source snapshot without asking
|
|
1107
1586
|
// each step to independently select a document target; otherwise a plan
|
|
1108
1587
|
// with multiple authored entity kinds could be rejected for an ambiguous
|
|
1109
|
-
// per-entity path after its source batch has already been validated.
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1588
|
+
// per-entity path after its source batch has already been validated. One
|
|
1589
|
+
// clock reading stamps every step, so the journaled store payload is exactly
|
|
1590
|
+
// what validation produced.
|
|
1591
|
+
const now = context.clock();
|
|
1592
|
+
const operationContext = {
|
|
1593
|
+
...context,
|
|
1594
|
+
prolog,
|
|
1595
|
+
sourceFirst: false,
|
|
1596
|
+
clock: () => now,
|
|
1597
|
+
};
|
|
1113
1598
|
const statusResult = await executeStatus({}, operationContext);
|
|
1114
1599
|
const status = statusResult.structuredContent;
|
|
1115
1600
|
if (!status)
|
|
@@ -1125,77 +1610,55 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
|
|
|
1125
1610
|
throw new Error("Apply plan failed: workspace snapshot changed since compilation");
|
|
1126
1611
|
const sourceHashesChecked = await validateSources(operationContext, args.plan.expected.sourceHashes);
|
|
1127
1612
|
const steps = args.plan.steps.map((step) => asUpsert(step));
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1613
|
+
// implements REQ-kibi-truthful-consistency
|
|
1614
|
+
// Run every step through the same validation chain executeUpsert runs,
|
|
1615
|
+
// then stage all of them together in a rolled-back transaction, before the
|
|
1616
|
+
// first write. A step executeUpsert would reject, or a final state that
|
|
1617
|
+
// introduces a contradiction or an infeasible success scenario, fails the
|
|
1618
|
+
// whole plan up front. Limits of the staged emulation:
|
|
1619
|
+
// - symbol granularity reads source code from the workspace as it is now;
|
|
1620
|
+
// the plan's sourceWrites are validated again once they are published,
|
|
1621
|
+
// before the store commit, and a failure then rolls them back;
|
|
1622
|
+
// - staged entities are the validated plan payloads; fields executeUpsert
|
|
1623
|
+
// derives while committing source-first (canonical symbol re-extraction,
|
|
1624
|
+
// chosen source paths) do not arise, because compile plans apply with
|
|
1625
|
+
// sourceFirst disabled;
|
|
1626
|
+
// - the store's own checks (entity schema, relationship endpoints,
|
|
1627
|
+
// requirement contradictions) run again inside the batch transaction, so
|
|
1628
|
+
// state another writer changed after this preflight aborts the whole
|
|
1629
|
+
// batch instead of committing part of it.
|
|
1630
|
+
let validated;
|
|
1138
1631
|
try {
|
|
1139
|
-
|
|
1140
|
-
const result = await executeUpsert(step, operationContext);
|
|
1141
|
-
appliedSteps += 1;
|
|
1142
|
-
// Each step's compiled commit is an authoritative mutation boundary.
|
|
1143
|
-
// Mark it immediately so a later step's failure can never be mistaken
|
|
1144
|
-
// for a retryable whole-plan failure.
|
|
1145
|
-
onCommitted();
|
|
1146
|
-
const payload = result.structuredContent;
|
|
1147
|
-
if (payload && typeof payload === "object") {
|
|
1148
|
-
const row = payload;
|
|
1149
|
-
changedEntities += Number(row.created ?? 0) + Number(row.updated ?? 0);
|
|
1150
|
-
changedRelationships += Number(row.relationships_created ?? 0);
|
|
1151
|
-
const details = payload;
|
|
1152
|
-
if (Array.isArray(details.effectFailures)) {
|
|
1153
|
-
effectFailures.push(...details.effectFailures.filter(isRecord));
|
|
1154
|
-
}
|
|
1155
|
-
if (Array.isArray(details.nextActions)) {
|
|
1156
|
-
nextActions.push(...details.nextActions.filter(isRecord));
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
compiledCommit = true;
|
|
1161
|
-
onCommitted();
|
|
1632
|
+
validated = await validatePlanSteps(steps, operationContext);
|
|
1162
1633
|
}
|
|
1163
1634
|
catch (error) {
|
|
1164
|
-
|
|
1165
|
-
effectFailures.push({
|
|
1166
|
-
kind: "compiled-store",
|
|
1167
|
-
errorCode: "DERIVED_COMMIT_FAILED",
|
|
1168
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
1169
|
-
});
|
|
1170
|
-
nextActions.push({
|
|
1171
|
-
operation: "kb_apply_plan",
|
|
1172
|
-
input: { recoveryJournalId: sourceWrites.journalId },
|
|
1173
|
-
reason: "Authoritative source files are committed but compiled effects failed; replay the recovery journal instead of retrying the original mutation.",
|
|
1174
|
-
required: true,
|
|
1175
|
-
});
|
|
1176
|
-
onCommitted();
|
|
1177
|
-
await markSourceJournal(operationContext, sourceWrites.journalId, "repair_required");
|
|
1178
|
-
}
|
|
1179
|
-
else if (appliedSteps > 0) {
|
|
1180
|
-
// A journalless plan (sourceWrites: []) already committed earlier steps
|
|
1181
|
-
// when a later step failed. Expose an explicit non-retryable
|
|
1182
|
-
// partial-commit error instead of a generic exception that invites a
|
|
1183
|
-
// blind retry of the whole plan.
|
|
1184
|
-
if (error instanceof OperationError)
|
|
1185
|
-
throw error;
|
|
1186
|
-
throw new OperationError("PARTIAL_COMMIT_REPAIR_REQUIRED", `Plan ${args.plan.planHash} committed ${appliedSteps} of ${steps.length} step(s) before failing: ${error instanceof Error ? error.message : String(error)}. This plan carries no recovery journal; do not retry the original plan; inspect the committed snapshot and apply a fresh plan for the remaining steps`, false);
|
|
1187
|
-
}
|
|
1188
|
-
else {
|
|
1189
|
-
throw error;
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
if (compiledCommit) {
|
|
1193
|
-
await markSourceJournal(operationContext, sourceWrites.journalId, "compiled_published");
|
|
1635
|
+
throw new Error(`Apply plan failed before any write: ${errorMessage(error)}`);
|
|
1194
1636
|
}
|
|
1637
|
+
const preflight = await prolog.query(planWhatIfGoal(args.plan.steps, now));
|
|
1638
|
+
if (!preflight.success)
|
|
1639
|
+
throw new Error(`Apply plan failed before any write: the store rejected the staged plan: ${preflight.error ?? "unknown error"}`);
|
|
1640
|
+
const staged = parseWhatIfAnalysis(preflight.bindings.JsonString);
|
|
1641
|
+
if (staged === null)
|
|
1642
|
+
throw new Error("Apply plan failed before any write: the staged plan's contradiction analysis could not be read");
|
|
1643
|
+
const introducedBlocking = staged.introduced.filter(isBlockingWitness);
|
|
1644
|
+
if (introducedBlocking.length > 0)
|
|
1645
|
+
throw new Error(`Apply plan failed before any write: the staged plan introduces ${introducedBlocking.length} contradiction(s) or infeasible scenario(s): ${introducedBlocking
|
|
1646
|
+
.map((witness) => witness.reason || witness.requirements.join("/"))
|
|
1647
|
+
.join("; ")}`);
|
|
1648
|
+
const commit = await commitCompilePlan(operationContext, prolog, args.plan, validated, async () => {
|
|
1649
|
+
await validatePlanSteps(steps, operationContext);
|
|
1650
|
+
}, now, onCommitted);
|
|
1651
|
+
const effectFailures = [...commit.effectFailures];
|
|
1652
|
+
const nextActions = [...commit.nextActions];
|
|
1653
|
+
const notes = [
|
|
1654
|
+
commit.journalId === null
|
|
1655
|
+
? "Plan steps were validated together and committed in one store transaction."
|
|
1656
|
+
: `Plan steps were validated together, journaled in ${commit.journalId}, and committed in one store transaction; source writes are restored from the journal if the commit fails.`,
|
|
1657
|
+
];
|
|
1195
1658
|
// Everything before this point is authoritative. A status/workspace
|
|
1196
1659
|
// readback failure therefore cannot turn the operation into a retryable
|
|
1197
|
-
// mutation: return a repairable
|
|
1198
|
-
//
|
|
1660
|
+
// mutation: return a repairable completion with deterministic next actions
|
|
1661
|
+
// instead.
|
|
1199
1662
|
let finalStatus;
|
|
1200
1663
|
let finalWorkspace;
|
|
1201
1664
|
let postCommitFailure;
|
|
@@ -1211,7 +1674,7 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
|
|
|
1211
1674
|
}
|
|
1212
1675
|
}
|
|
1213
1676
|
catch (error) {
|
|
1214
|
-
postCommitFailure =
|
|
1677
|
+
postCommitFailure = errorMessage(error);
|
|
1215
1678
|
effectFailures.push({
|
|
1216
1679
|
kind: "post-commit-readback",
|
|
1217
1680
|
errorCode: "POST_COMMIT_READBACK_FAILED",
|
|
@@ -1226,7 +1689,6 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
|
|
|
1226
1689
|
reason: "After status is readable, run the consistency checks and follow their typed repair actions.",
|
|
1227
1690
|
required: true,
|
|
1228
1691
|
});
|
|
1229
|
-
await markSourceJournal(operationContext, sourceWrites.journalId, "repair_required");
|
|
1230
1692
|
}
|
|
1231
1693
|
const finalSnapshots = finalStatus !== undefined && finalWorkspace?.available === true
|
|
1232
1694
|
? {
|
|
@@ -1243,9 +1705,9 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
|
|
|
1243
1705
|
version: PLAN_APPLY_RESULT_VERSION,
|
|
1244
1706
|
outcome: "applied",
|
|
1245
1707
|
planHash: args.plan.planHash,
|
|
1246
|
-
changedEntities,
|
|
1247
|
-
changedRelationships,
|
|
1248
|
-
changedPaths:
|
|
1708
|
+
changedEntities: commit.changedEntities,
|
|
1709
|
+
changedRelationships: commit.changedRelationships,
|
|
1710
|
+
changedPaths: [...commit.documentPaths],
|
|
1249
1711
|
finalSnapshots,
|
|
1250
1712
|
validationSummary: {
|
|
1251
1713
|
stepsValidated: steps.length,
|
|
@@ -1253,7 +1715,7 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
|
|
|
1253
1715
|
sourceHashesChecked,
|
|
1254
1716
|
notes,
|
|
1255
1717
|
},
|
|
1256
|
-
recoveryJournalId:
|
|
1718
|
+
recoveryJournalId: commit.journalId,
|
|
1257
1719
|
...(effectFailures.length > 0 || postCommitFailure !== undefined
|
|
1258
1720
|
? {
|
|
1259
1721
|
status: "committed_with_repairs",
|
|
@@ -1266,14 +1728,155 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
|
|
|
1266
1728
|
content: [
|
|
1267
1729
|
{
|
|
1268
1730
|
type: "text",
|
|
1269
|
-
text: `Applied plan ${args.plan.planHash.slice(0, 12)}
|
|
1731
|
+
text: `Applied plan ${args.plan.planHash.slice(0, 12)}: ${steps.length} step(s) committed in one store transaction.`,
|
|
1270
1732
|
},
|
|
1271
1733
|
],
|
|
1272
1734
|
structuredContent: payload,
|
|
1273
1735
|
};
|
|
1274
1736
|
}
|
|
1275
|
-
// implements REQ-
|
|
1737
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-core-atomic-upsert-persistence
|
|
1738
|
+
/** The kb_apply_plan result for a plan journal this call recovered or found finished. */
|
|
1739
|
+
async function planJournalRecoveryResult(context, recovery, journal) {
|
|
1740
|
+
let finalSnapshots = {
|
|
1741
|
+
branch: journal.branch,
|
|
1742
|
+
kbSnapshotId: "recovered",
|
|
1743
|
+
workspaceSnapshot: "recovered",
|
|
1744
|
+
};
|
|
1745
|
+
try {
|
|
1746
|
+
const status = (await executeStatus({}, context)).structuredContent;
|
|
1747
|
+
const workspace = await readWorkspaceSnapshot(context);
|
|
1748
|
+
if (status !== undefined && workspace.available) {
|
|
1749
|
+
finalSnapshots = {
|
|
1750
|
+
branch: status.branch,
|
|
1751
|
+
kbSnapshotId: status.snapshotId,
|
|
1752
|
+
workspaceSnapshot: workspace.snapshot.hash,
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
catch {
|
|
1757
|
+
// Snapshot readback is informational after a recovery.
|
|
1758
|
+
}
|
|
1759
|
+
const completedNow = recovery.action === "completed";
|
|
1760
|
+
return {
|
|
1761
|
+
content: [{ type: "text", text: recovery.detail }],
|
|
1762
|
+
structuredContent: {
|
|
1763
|
+
version: PLAN_APPLY_RESULT_VERSION,
|
|
1764
|
+
outcome: recovery.state === "committed" ? "replayed" : "rolled_back",
|
|
1765
|
+
planHash: recovery.planHash,
|
|
1766
|
+
changedEntities: completedNow ? journal.summary.changedEntities : 0,
|
|
1767
|
+
changedRelationships: completedNow
|
|
1768
|
+
? journal.summary.changedRelationships
|
|
1769
|
+
: 0,
|
|
1770
|
+
changedPaths: recovery.restoredPaths,
|
|
1771
|
+
finalSnapshots,
|
|
1772
|
+
validationSummary: {
|
|
1773
|
+
stepsValidated: 0,
|
|
1774
|
+
stepsApplied: completedNow ? journal.summary.changedEntities : 0,
|
|
1775
|
+
sourceHashesChecked: journal.summary.filePaths.length,
|
|
1776
|
+
notes: [recovery.detail],
|
|
1777
|
+
},
|
|
1778
|
+
recoveryJournalId: recovery.journalId,
|
|
1779
|
+
...(recovery.receiptFailure !== undefined
|
|
1780
|
+
? {
|
|
1781
|
+
status: "committed_with_repairs",
|
|
1782
|
+
effectFailures: [
|
|
1783
|
+
{
|
|
1784
|
+
kind: "pending-source-receipt",
|
|
1785
|
+
errorCode: "PENDING_SOURCE_RECEIPT_FAILED",
|
|
1786
|
+
detail: recovery.receiptFailure,
|
|
1787
|
+
},
|
|
1788
|
+
],
|
|
1789
|
+
nextActions: [
|
|
1790
|
+
{
|
|
1791
|
+
operation: "kb_apply_plan",
|
|
1792
|
+
input: { recoveryJournalId: recovery.journalId },
|
|
1793
|
+
reason: "The plan is committed, but binding its new source files to pending-source receipts failed; recover the journal again to finish them.",
|
|
1794
|
+
required: true,
|
|
1795
|
+
},
|
|
1796
|
+
],
|
|
1797
|
+
}
|
|
1798
|
+
: {}),
|
|
1799
|
+
},
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-core-atomic-upsert-persistence
|
|
1803
|
+
async function executePlanJournalRecovery(journalId, context, recovered) {
|
|
1804
|
+
if (!context.fs)
|
|
1805
|
+
throw new Error("Plan journal recovery requires a filesystem-capable runtime");
|
|
1806
|
+
const handle = openPlanApplyJournalById(context, journalId);
|
|
1807
|
+
const recovery = recovered.find((candidate) => candidate.journalId === journalId) ??
|
|
1808
|
+
(await recoverPlanApplyJournal(context, handle, {
|
|
1809
|
+
prolog: async () => context.prolog ?? (await context.ensureProlog?.()),
|
|
1810
|
+
}));
|
|
1811
|
+
return planJournalRecoveryResult(context, recovery, handle.journal);
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Finish every interrupted plan application of the active branch before
|
|
1815
|
+
* this call reads or writes anything.
|
|
1816
|
+
*/
|
|
1817
|
+
async function recoverInterruptedPlans(context, explicitJournalId) {
|
|
1818
|
+
if (!context.fs)
|
|
1819
|
+
return [];
|
|
1820
|
+
return settlePendingPlanApplyJournals(context, {
|
|
1821
|
+
prolog: async () => context.prolog ?? (await context.ensureProlog?.()),
|
|
1822
|
+
}, explicitJournalId);
|
|
1823
|
+
}
|
|
1824
|
+
/** Report recoveries this call performed alongside its own result. */
|
|
1825
|
+
function withRecoveryReport(result, recovered) {
|
|
1826
|
+
const structured = result.structuredContent;
|
|
1827
|
+
const own = typeof structured.recoveryJournalId === "string"
|
|
1828
|
+
? structured.recoveryJournalId
|
|
1829
|
+
: undefined;
|
|
1830
|
+
const reported = recovered.filter((recovery) => recovery.action !== "none" && recovery.journalId !== own);
|
|
1831
|
+
if (reported.length === 0)
|
|
1832
|
+
return result;
|
|
1833
|
+
const notes = reported.map((recovery) => recovery.detail);
|
|
1834
|
+
const [first, ...rest] = result.content;
|
|
1835
|
+
const content = [
|
|
1836
|
+
{
|
|
1837
|
+
type: "text",
|
|
1838
|
+
text: [first?.text ?? "", ...notes].join(" ").trim(),
|
|
1839
|
+
},
|
|
1840
|
+
...rest,
|
|
1841
|
+
];
|
|
1842
|
+
const recoveredJournals = reported.map((recovery) => ({
|
|
1843
|
+
journalId: recovery.journalId,
|
|
1844
|
+
planHash: recovery.planHash,
|
|
1845
|
+
action: recovery.action,
|
|
1846
|
+
restoredPaths: recovery.restoredPaths,
|
|
1847
|
+
}));
|
|
1848
|
+
if (isRecord(structured.validationSummary)) {
|
|
1849
|
+
const summary = structured.validationSummary;
|
|
1850
|
+
return {
|
|
1851
|
+
content,
|
|
1852
|
+
structuredContent: {
|
|
1853
|
+
...structured,
|
|
1854
|
+
validationSummary: {
|
|
1855
|
+
...summary,
|
|
1856
|
+
notes: [
|
|
1857
|
+
...(Array.isArray(summary.notes) ? summary.notes : []),
|
|
1858
|
+
...notes,
|
|
1859
|
+
],
|
|
1860
|
+
recoveredJournals,
|
|
1861
|
+
},
|
|
1862
|
+
},
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
if (Array.isArray(structured.notes)) {
|
|
1866
|
+
return {
|
|
1867
|
+
content,
|
|
1868
|
+
structuredContent: {
|
|
1869
|
+
...structured,
|
|
1870
|
+
notes: [...structured.notes, ...notes],
|
|
1871
|
+
},
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
return { content, structuredContent: result.structuredContent };
|
|
1875
|
+
}
|
|
1876
|
+
// implements REQ-agent-guided-migration-orchestration, REQ-cli-canonical-runtime, REQ-KIBI-BOOTSTRAP-PLAN, REQ-kibi-change-to-proof-plan-compiler-v2, REQ-kibi-predicate-vocabulary-migration
|
|
1276
1877
|
export async function executeApplyPlan(args, context) {
|
|
1878
|
+
if (isBootstrapApplyArgs(args))
|
|
1879
|
+
preflightBootstrapActions(validateBootstrapPlanShape(args), context);
|
|
1277
1880
|
if (!context.fs || context.sourceMutationLockHeld === true)
|
|
1278
1881
|
return executeApplyPlanUnlocked(args, context);
|
|
1279
1882
|
const lock = await acquireWorkspaceMutationLock(context.workspaceRoot);
|
|
@@ -1513,6 +2116,10 @@ async function applyMigrationAction(action, context) {
|
|
|
1513
2116
|
await applyPredicateSchemaAlignment(action, context);
|
|
1514
2117
|
return;
|
|
1515
2118
|
default:
|
|
2119
|
+
if (SCHEMA6_AUTOMATIC_CODES.has(action.code)) {
|
|
2120
|
+
await applySchema6MigrationAction(action, context);
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
1516
2123
|
throw new Error(`Migration action '${action.code}' has no automatic executor.`);
|
|
1517
2124
|
}
|
|
1518
2125
|
}
|