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