kibi-cli 2.1.0 → 2.3.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-operation-loader.d.ts.map +1 -1
- package/dist/cli-operation-loader.js +3 -0
- package/dist/cli-operation-metadata.d.ts +4 -0
- package/dist/cli-operation-metadata.d.ts.map +1 -1
- package/dist/cli-operation-metadata.js +5 -0
- package/dist/cli-protocol.d.ts.map +1 -1
- package/dist/cli-protocol.js +3 -2
- package/dist/cli-register-reporting.d.ts.map +1 -1
- package/dist/cli-register-reporting.js +4 -0
- package/dist/commands/aggregated-checks.d.ts +7 -0
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +25 -1
- package/dist/commands/check-diff.d.ts +5 -0
- package/dist/commands/check-diff.d.ts.map +1 -0
- package/dist/commands/check-diff.js +62 -0
- package/dist/commands/check-generated.d.ts.map +1 -1
- package/dist/commands/check-generated.js +41 -18
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +231 -86
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +168 -53
- package/dist/commands/github-init.d.ts +1 -1
- package/dist/commands/github-init.d.ts.map +1 -1
- package/dist/commands/github-init.js +10 -3
- package/dist/commands/init-helpers.d.ts +17 -2
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +123 -23
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +110 -15
- package/dist/commands/prove.d.ts.map +1 -1
- package/dist/commands/prove.js +10 -18
- package/dist/commands/sync/cache.d.ts +20 -1
- package/dist/commands/sync/cache.d.ts.map +1 -1
- package/dist/commands/sync/cache.js +51 -2
- package/dist/commands/sync/discovery.d.ts.map +1 -1
- package/dist/commands/sync/discovery.js +1 -1
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +3 -1
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +8 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +11 -3
- package/dist/engine.d.ts +41 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +377 -80
- package/dist/extractors/manifest.d.ts.map +1 -1
- package/dist/extractors/manifest.js +66 -28
- package/dist/extractors/markdown.d.ts +2 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +14 -3
- package/dist/extractors/relationships.d.ts.map +1 -1
- package/dist/extractors/relationships.js +1 -0
- package/dist/extractors/symbols-coordinator.d.ts +7 -0
- package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
- package/dist/extractors/symbols-coordinator.js +75 -39
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +68 -29
- package/dist/operations/bootstrap/generate.d.ts.map +1 -1
- package/dist/operations/bootstrap/generate.js +4 -3
- package/dist/operations/modeling/model-requirement.d.ts +1 -1
- package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
- package/dist/operations/modeling/model-requirement.js +176 -10
- package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-applyplan.js +14 -2
- package/dist/operations/modeling/predicate-inference-1.js +1 -1
- package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-loader.js +24 -0
- package/dist/operations/modeling/predicate-types.d.ts +4 -0
- package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-vocabulary.d.ts +54 -0
- package/dist/operations/modeling/predicate-vocabulary.d.ts.map +1 -0
- package/dist/operations/modeling/predicate-vocabulary.js +169 -0
- package/dist/operations/modeling/requirement-types.d.ts +5 -0
- package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
- package/dist/operations/modeling/vocabulary-alignment.d.ts +60 -0
- package/dist/operations/modeling/vocabulary-alignment.d.ts.map +1 -0
- package/dist/operations/modeling/vocabulary-alignment.js +178 -0
- package/dist/operations/mutation/contradictions.d.ts +3 -0
- package/dist/operations/mutation/contradictions.d.ts.map +1 -1
- package/dist/operations/mutation/contradictions.js +29 -0
- package/dist/operations/mutation/delete.d.ts.map +1 -1
- package/dist/operations/mutation/delete.js +33 -1
- package/dist/operations/mutation/predicate-vocabulary-guard.d.ts +8 -0
- package/dist/operations/mutation/predicate-vocabulary-guard.d.ts.map +1 -0
- package/dist/operations/mutation/predicate-vocabulary-guard.js +64 -0
- package/dist/operations/mutation/relationships.d.ts +1 -1
- package/dist/operations/mutation/relationships.d.ts.map +1 -1
- package/dist/operations/mutation/relationships.js +1 -0
- package/dist/operations/mutation/saga.d.ts +5 -0
- package/dist/operations/mutation/saga.d.ts.map +1 -1
- package/dist/operations/mutation/saga.js +10 -0
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +2 -0
- 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 +213 -52
- package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
- package/dist/operations/mutation/strict-fact.js +5 -0
- package/dist/operations/mutation/types.d.ts +17 -0
- package/dist/operations/mutation/types.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.d.ts +6 -0
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +77 -11
- package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
- package/dist/operations/mutation/validate-upsert.js +24 -1
- package/dist/operations/mutation/validation.d.ts.map +1 -1
- package/dist/operations/mutation/validation.js +16 -3
- package/dist/operations/planning/apply-plan.d.ts.map +1 -1
- package/dist/operations/planning/apply-plan.js +66 -26
- package/dist/operations/planning/compile-intent.d.ts.map +1 -1
- package/dist/operations/planning/compile-intent.js +3 -1
- package/dist/operations/proof/code-scope.d.ts +17 -0
- package/dist/operations/proof/code-scope.d.ts.map +1 -0
- package/dist/operations/proof/code-scope.js +60 -0
- package/dist/operations/proof/ingest-proof.d.ts +4 -2
- package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
- package/dist/operations/proof/ingest-proof.js +208 -74
- package/dist/operations/proof/migrate-legacy-receipts.d.ts.map +1 -1
- package/dist/operations/proof/migrate-legacy-receipts.js +7 -5
- package/dist/operations/proof/prune-receipts.d.ts.map +1 -1
- package/dist/operations/proof/prune-receipts.js +7 -5
- package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analysis-receipt.js +7 -1
- package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.js +26 -4
- 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 +7 -1
- package/dist/performance-trace.d.ts +17 -0
- package/dist/performance-trace.d.ts.map +1 -0
- package/dist/performance-trace.js +134 -0
- package/dist/plugins/approved-source-analyzers.d.ts +13 -0
- package/dist/plugins/approved-source-analyzers.d.ts.map +1 -0
- package/dist/plugins/approved-source-analyzers.generated.d.ts +71 -0
- package/dist/plugins/approved-source-analyzers.generated.d.ts.map +1 -0
- package/dist/plugins/approved-source-analyzers.generated.js +77 -0
- package/dist/plugins/approved-source-analyzers.js +3 -0
- package/dist/plugins/compose-semantic-classifier.d.ts +6 -1
- package/dist/plugins/compose-semantic-classifier.d.ts.map +1 -1
- package/dist/plugins/compose-semantic-classifier.js +8 -3
- package/dist/plugins/compose-vocabulary-alignment.d.ts +23 -0
- package/dist/plugins/compose-vocabulary-alignment.d.ts.map +1 -0
- package/dist/plugins/compose-vocabulary-alignment.js +141 -0
- package/dist/plugins/external-allowlist.d.ts +7 -0
- package/dist/plugins/external-allowlist.d.ts.map +1 -1
- package/dist/plugins/external-allowlist.js +13 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +4 -0
- package/dist/plugins/maintenance-source-analysis.d.ts +14 -0
- package/dist/plugins/maintenance-source-analysis.d.ts.map +1 -0
- package/dist/plugins/maintenance-source-analysis.js +191 -0
- package/dist/plugins/registry.d.ts +3 -1
- package/dist/plugins/registry.d.ts.map +1 -1
- package/dist/plugins/registry.js +27 -1
- package/dist/plugins/source-analysis-service.d.ts +36 -1
- package/dist/plugins/source-analysis-service.d.ts.map +1 -1
- package/dist/plugins/source-analysis-service.js +184 -27
- package/dist/plugins/source-change-analysis.d.ts +15 -0
- package/dist/plugins/source-change-analysis.d.ts.map +1 -0
- package/dist/plugins/source-change-analysis.js +54 -0
- package/dist/plugins/source-classification.d.ts +41 -0
- package/dist/plugins/source-classification.d.ts.map +1 -0
- package/dist/plugins/source-classification.js +490 -0
- package/dist/prolog/codec.d.ts.map +1 -1
- package/dist/prolog/codec.js +2 -0
- package/dist/prolog/swipl-resolver.d.ts +88 -0
- package/dist/prolog/swipl-resolver.d.ts.map +1 -0
- package/dist/prolog/swipl-resolver.js +446 -0
- package/dist/prolog.d.ts +51 -1
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +145 -15
- package/dist/public/diagnostic-usage.d.ts.map +1 -1
- package/dist/public/diagnostic-usage.js +7 -25
- package/dist/public/impact/analyzer.d.ts.map +1 -1
- package/dist/public/impact/analyzer.js +56 -5
- package/dist/public/impact/diagnostics.d.ts.map +1 -1
- package/dist/public/impact/diagnostics.js +3 -1
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
- package/dist/public/impact/full-kb-quality.js +4 -12
- package/dist/public/impact/manifest.d.ts.map +1 -1
- package/dist/public/impact/manifest.js +15 -1
- package/dist/public/impact/source-changes.d.ts.map +1 -1
- package/dist/public/impact/source-changes.js +35 -27
- package/dist/public/impact/types.d.ts +2 -0
- package/dist/public/impact/types.d.ts.map +1 -1
- package/dist/public/operations/catalog.d.ts +80 -7
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/catalog.js +2 -0
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +10 -2
- package/dist/public/operations/check-helpers.d.ts +6 -0
- package/dist/public/operations/check-helpers.d.ts.map +1 -1
- package/dist/public/operations/check-helpers.js +21 -1
- package/dist/public/operations/contracts.d.ts.map +1 -1
- package/dist/public/operations/contracts.js +325 -8
- package/dist/public/operations/discovery-entities.d.ts +42 -2
- package/dist/public/operations/discovery-entities.d.ts.map +1 -1
- package/dist/public/operations/discovery-entities.js +195 -22
- package/dist/public/operations/discovery-executors.d.ts +1 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +75 -46
- package/dist/public/operations/impact-review-preparation.d.ts +14 -0
- package/dist/public/operations/impact-review-preparation.d.ts.map +1 -0
- package/dist/public/operations/impact-review-preparation.js +379 -0
- package/dist/public/operations/index.d.ts +2 -0
- package/dist/public/operations/index.d.ts.map +1 -1
- package/dist/public/operations/index.js +2 -0
- package/dist/public/operations/migration-plan.d.ts +2 -0
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +50 -0
- package/dist/public/operations/node-ports.d.ts.map +1 -1
- package/dist/public/operations/node-ports.js +1 -35
- package/dist/public/operations/predicate-schema-conformance.d.ts +41 -0
- package/dist/public/operations/predicate-schema-conformance.d.ts.map +1 -0
- package/dist/public/operations/predicate-schema-conformance.js +242 -0
- package/dist/public/operations/prolog-json.d.ts.map +1 -1
- package/dist/public/operations/prolog-json.js +9 -3
- package/dist/public/operations/proof-receipt-projection.d.ts +5 -0
- package/dist/public/operations/proof-receipt-projection.d.ts.map +1 -0
- package/dist/public/operations/proof-receipt-projection.js +36 -0
- package/dist/public/operations/result-envelope.d.ts +34 -1
- package/dist/public/operations/result-envelope.d.ts.map +1 -1
- package/dist/public/operations/result-envelope.js +78 -2
- package/dist/public/operations/runtime-types.d.ts +1 -1
- package/dist/public/operations/runtime-types.d.ts.map +1 -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 +8 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/discovery.d.ts +7 -1
- package/dist/public/operations/specs/discovery.d.ts.map +1 -1
- package/dist/public/operations/specs/discovery.js +7 -1
- package/dist/public/operations/specs/impact-review-preparation.d.ts +47 -0
- package/dist/public/operations/specs/impact-review-preparation.d.ts.map +1 -0
- package/dist/public/operations/specs/impact-review-preparation.js +43 -0
- package/dist/public/operations/specs/mutation-schemas.d.ts +27 -4
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +21 -3
- package/dist/public/operations/specs/reporting.d.ts +8 -0
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +46 -15
- package/dist/public/operations/specs/upsert.d.ts +28 -5
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/operations/specs/upsert.js +1 -1
- package/dist/public/operations/symbol-repair-plan.d.ts.map +1 -1
- package/dist/public/operations/symbol-repair-plan.js +8 -3
- package/dist/public/operations/types.d.ts +1 -1
- package/dist/public/operations/types.d.ts.map +1 -1
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +22 -0
- package/dist/public/schemas/relationship.d.ts.map +1 -1
- package/dist/public/schemas/relationship.js +1 -0
- package/dist/public/skills/kibi-usage/SKILL.md +7 -3
- package/dist/public/skills/kibi-usage/resources/fact-lanes.md +4 -0
- package/dist/public/skills/kibi-usage/resources/operation-access.md +1 -0
- package/dist/public/skills/kibi-usage/resources/relationship-directions.md +35 -17
- package/dist/public/skills/kibi-usage/resources/source-authoring.md +36 -0
- package/dist/public/skills/kibi-usage/resources/workflows.md +52 -1
- package/dist/public/telemetry-acceptance.d.ts.map +1 -1
- package/dist/public/telemetry-acceptance.js +21 -5
- package/dist/relationships/shards.d.ts +6 -0
- package/dist/relationships/shards.d.ts.map +1 -1
- package/dist/relationships/shards.js +26 -19
- package/dist/schemas/entity.schema.json +32 -0
- package/dist/traceability/analysis-gate.d.ts +39 -0
- package/dist/traceability/analysis-gate.d.ts.map +1 -0
- package/dist/traceability/analysis-gate.js +51 -0
- package/dist/traceability/git-change-snapshot.d.ts +27 -0
- package/dist/traceability/git-change-snapshot.d.ts.map +1 -0
- package/dist/traceability/git-change-snapshot.js +411 -0
- package/dist/traceability/git-staged.d.ts +5 -1
- package/dist/traceability/git-staged.d.ts.map +1 -1
- package/dist/traceability/git-staged.js +16 -11
- package/dist/traceability/impact-evaluator.d.ts +54 -0
- package/dist/traceability/impact-evaluator.d.ts.map +1 -0
- package/dist/traceability/impact-evaluator.js +270 -0
- package/dist/traceability/impact-git-tree.d.ts +12 -0
- package/dist/traceability/impact-git-tree.d.ts.map +1 -0
- package/dist/traceability/impact-git-tree.js +46 -0
- package/dist/traceability/impact-knowledge.d.ts +16 -0
- package/dist/traceability/impact-knowledge.d.ts.map +1 -0
- package/dist/traceability/impact-knowledge.js +165 -0
- package/dist/traceability/impact-policy.d.ts +15 -0
- package/dist/traceability/impact-policy.d.ts.map +1 -0
- package/dist/traceability/impact-policy.js +48 -0
- package/dist/traceability/impact-policy.v1.schema.json +48 -0
- package/dist/traceability/impact-pr-diff.d.ts +5 -0
- package/dist/traceability/impact-pr-diff.d.ts.map +1 -0
- package/dist/traceability/impact-pr-diff.js +83 -0
- package/dist/traceability/impact-record-validation.d.ts +9 -0
- package/dist/traceability/impact-record-validation.d.ts.map +1 -0
- package/dist/traceability/impact-record-validation.js +113 -0
- package/dist/traceability/impact-review.d.ts +148 -0
- package/dist/traceability/impact-review.d.ts.map +1 -0
- package/dist/traceability/impact-review.js +494 -0
- package/dist/traceability/impact-review.schema.json +365 -0
- package/dist/traceability/impact-side-evidence.d.ts +13 -0
- package/dist/traceability/impact-side-evidence.d.ts.map +1 -0
- package/dist/traceability/impact-side-evidence.js +240 -0
- package/dist/traceability/reviewed-coordinate-refresh.d.ts +10 -0
- package/dist/traceability/reviewed-coordinate-refresh.d.ts.map +1 -0
- package/dist/traceability/reviewed-coordinate-refresh.js +49 -0
- package/dist/traceability/snapshot-knowledge.d.ts +4 -0
- package/dist/traceability/snapshot-knowledge.d.ts.map +1 -0
- package/dist/traceability/snapshot-knowledge.js +98 -0
- package/dist/traceability/staged-diagnostics.d.ts +1 -1
- package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
- package/dist/traceability/staged-entity-id-style.d.ts +12 -0
- package/dist/traceability/staged-entity-id-style.d.ts.map +1 -0
- package/dist/traceability/staged-entity-id-style.js +68 -0
- package/dist/traceability/staged-file-coverage.d.ts +23 -0
- package/dist/traceability/staged-file-coverage.d.ts.map +1 -1
- package/dist/traceability/staged-impact-contract.d.ts.map +1 -1
- package/dist/traceability/staged-impact-contract.js +2 -16
- package/dist/traceability/staged-symbols-manifest.d.ts +4 -0
- package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +17 -6
- package/dist/traceability/symbol-extract.d.ts +3 -5
- package/dist/traceability/symbol-extract.d.ts.map +1 -1
- package/dist/traceability/symbol-extract.js +49 -34
- package/dist/traceability/temp-kb.d.ts +1 -1
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +57 -13
- package/dist/traceability/trusted-pr-event.d.ts +13 -0
- package/dist/traceability/trusted-pr-event.d.ts.map +1 -0
- package/dist/traceability/trusted-pr-event.js +105 -0
- package/dist/traceability/validate.js +1 -1
- package/dist/types/entities.d.ts +2 -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/canonical-path.d.ts +3 -0
- package/dist/utils/canonical-path.d.ts.map +1 -0
- package/dist/utils/canonical-path.js +33 -0
- package/dist/utils/entity-id-style.d.ts +27 -0
- package/dist/utils/entity-id-style.d.ts.map +1 -0
- package/dist/utils/entity-id-style.js +81 -0
- package/dist/utils/git-repository-context.d.ts +50 -0
- package/dist/utils/git-repository-context.d.ts.map +1 -0
- package/dist/utils/git-repository-context.js +259 -0
- package/dist/utils/rule-registry.d.ts +5 -0
- package/dist/utils/rule-registry.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.d.ts +37 -0
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +44 -0
- package/dist/utils/strict-modeling.d.ts +7 -0
- package/dist/utils/strict-modeling.d.ts.map +1 -1
- package/dist/utils/strict-modeling.js +11 -0
- package/package.json +14 -5
- package/schema/entities.pl +60 -1
- package/schema/relationships.pl +17 -4
- package/schema/validation.pl +118 -20
- package/src/public/diagnostic-usage.ts +16 -24
- package/src/public/impact/analyzer.ts +88 -5
- package/src/public/impact/diagnostics.ts +7 -5
- package/src/public/impact/full-kb-quality.ts +4 -16
- package/src/public/impact/manifest.ts +14 -1
- package/src/public/impact/source-changes.ts +39 -34
- package/src/public/impact/types.ts +5 -0
- package/src/public/operations/catalog.ts +2 -0
- package/src/public/operations/check-executor.ts +12 -1
- package/src/public/operations/check-helpers.ts +27 -1
- package/src/public/operations/contracts.ts +404 -9
- package/src/public/operations/discovery-entities.ts +292 -23
- package/src/public/operations/discovery-executors.ts +91 -55
- package/src/public/operations/impact-review-preparation.ts +548 -0
- package/src/public/operations/index.ts +2 -0
- package/src/public/operations/migration-plan.ts +57 -0
- package/src/public/operations/node-ports.ts +1 -39
- package/src/public/operations/predicate-schema-conformance.ts +362 -0
- package/src/public/operations/prolog-json.ts +9 -3
- package/src/public/operations/proof-receipt-projection.ts +39 -0
- package/src/public/operations/result-envelope.ts +114 -2
- package/src/public/operations/runtime-types.ts +1 -1
- package/src/public/operations/specs/check-rules.generated.ts +8 -1
- package/src/public/operations/specs/discovery.ts +8 -1
- package/src/public/operations/specs/impact-review-preparation.ts +46 -0
- package/src/public/operations/specs/mutation-schemas.ts +23 -3
- package/src/public/operations/specs/reporting.ts +69 -22
- package/src/public/operations/specs/upsert.ts +1 -1
- package/src/public/operations/symbol-repair-plan.ts +13 -3
- package/src/public/operations/types.ts +1 -0
- package/src/public/schemas/entity.ts +22 -0
- package/src/public/schemas/relationship.ts +1 -0
- package/src/public/skills/kibi-usage/SKILL.md +7 -3
- package/src/public/skills/kibi-usage/resources/fact-lanes.md +4 -0
- package/src/public/skills/kibi-usage/resources/operation-access.md +1 -0
- package/src/public/skills/kibi-usage/resources/relationship-directions.md +35 -17
- package/src/public/skills/kibi-usage/resources/source-authoring.md +36 -0
- package/src/public/skills/kibi-usage/resources/workflows.md +52 -1
- package/src/public/telemetry-acceptance.ts +37 -6
- package/src/schemas/entity.schema.json +32 -0
- package/src/schemas/relationship.schema.json +1 -0
- package/src/traceability/impact-policy.v1.schema.json +48 -0
- package/src/traceability/impact-review.schema.json +365 -0
- package/templates/github/kibi-badge.yml +0 -4
- package/templates/github/kibi-report.yml +0 -4
package/dist/engine.js
CHANGED
|
@@ -6,20 +6,34 @@
|
|
|
6
6
|
* the local socket; Prolog itself never crosses the process boundary.
|
|
7
7
|
*/
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
|
-
import { createHash } from "node:crypto";
|
|
10
|
-
import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
9
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
10
|
+
import { chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
11
11
|
import * as net from "node:net";
|
|
12
12
|
import * as os from "node:os";
|
|
13
13
|
import * as path from "node:path";
|
|
14
|
+
import { performance } from "node:perf_hooks";
|
|
14
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { writePerformanceTraceEvent } from "./performance-trace.js";
|
|
15
17
|
import { PrologProcess, resolveKbPlPath } from "./prolog.js";
|
|
16
18
|
import { parseEntityFromList, parseListOfLists } from "./prolog/codec.js";
|
|
17
19
|
import { retryAttachAfterBreakingStaleLock } from "./prolog/store-lock.js";
|
|
20
|
+
import { resolveSwipl, swiplIdentity } from "./prolog/swipl-resolver.js";
|
|
21
|
+
import { queryEntityChunks } from "./public/operations/discovery-entities.js";
|
|
18
22
|
import { isValidBranchName } from "./utils/branch-resolver.js";
|
|
19
23
|
import { branchStorePath, ensureBranchStoreManifest, } from "./utils/branch-store-locator.js";
|
|
24
|
+
import { canonicalFilesystemPath } from "./utils/canonical-path.js";
|
|
20
25
|
export const ENGINE_PROTOCOL_VERSION = 1;
|
|
21
26
|
export const ENGINE_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
22
27
|
export const ENGINE_PACKAGE_VERSIONS = process.env.KIBI_PACKAGE_VERSIONS ?? "unknown";
|
|
28
|
+
/**
|
|
29
|
+
* Identity of the SWI-Prolog this process would launch (`<bin>@<version>`).
|
|
30
|
+
* The daemon and every client compare it; a daemon running a different
|
|
31
|
+
* resolved Prolog is replaced, never reused.
|
|
32
|
+
*/
|
|
33
|
+
// implements REQ-prolog-daemon-runtime-identity
|
|
34
|
+
export function engineSwiplIdentity() {
|
|
35
|
+
return swiplIdentity(resolveSwipl());
|
|
36
|
+
}
|
|
23
37
|
const ENGINE_QUERY_CACHE_MAX_ENTRIES = 128;
|
|
24
38
|
const ENGINE_QUERY_CACHE_MAX_RESULT_BYTES = 8 * 1024 * 1024;
|
|
25
39
|
const ENGINE_FRESHNESS_CACHE_MS = 100;
|
|
@@ -219,11 +233,19 @@ function frame(value) {
|
|
|
219
233
|
header.writeUInt32BE(payload.byteLength, 0);
|
|
220
234
|
return Buffer.concat([header, payload]);
|
|
221
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Swallow a write failure for a peer that is already gone.
|
|
238
|
+
*
|
|
239
|
+
* A socket write reports EPIPE/ECONNRESET asynchronously, so a surrounding
|
|
240
|
+
* try/catch cannot see it and Node raises it as an unhandled error. Passing a
|
|
241
|
+
* callback keeps the failure local to this write.
|
|
242
|
+
*/
|
|
243
|
+
function ignoreWriteFailure() { }
|
|
222
244
|
function writeSocketFrame(socket, value) {
|
|
223
245
|
if (socket.destroyed || !socket.writable)
|
|
224
246
|
return;
|
|
225
247
|
try {
|
|
226
|
-
socket.write(frame(value));
|
|
248
|
+
socket.write(frame(value), ignoreWriteFailure);
|
|
227
249
|
}
|
|
228
250
|
catch {
|
|
229
251
|
// A client may disconnect while a queued request is finishing. The
|
|
@@ -250,17 +272,75 @@ export function runtimeDirectory() {
|
|
|
250
272
|
const configured = process.env.KIBI_RUNTIME_DIR ??
|
|
251
273
|
process.env.XDG_RUNTIME_DIR ??
|
|
252
274
|
path.join(os.tmpdir(), "kibi-runtime");
|
|
253
|
-
|
|
275
|
+
// Windows named pipes have no Unix socket pathname budget. Preserve its
|
|
276
|
+
// filesystem location policy for the associated pid files.
|
|
277
|
+
if (process.platform === "win32") {
|
|
278
|
+
for (const candidate of [
|
|
279
|
+
configured,
|
|
280
|
+
path.join(os.tmpdir(), "kibi-runtime"),
|
|
281
|
+
]) {
|
|
282
|
+
try {
|
|
283
|
+
mkdirSync(candidate, { recursive: true, mode: 0o700 });
|
|
284
|
+
const probe = path.join(candidate, `.kibi-write-${process.pid}`);
|
|
285
|
+
writeFileSync(probe, "", { mode: 0o600 });
|
|
286
|
+
unlinkSync(probe);
|
|
287
|
+
return candidate;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Try the existing system-temp fallback.
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
throw new Error("Unable to create a writable Kibi engine runtime directory");
|
|
294
|
+
}
|
|
295
|
+
const euid = process.geteuid?.();
|
|
296
|
+
if (euid === undefined)
|
|
297
|
+
throw new Error("Kibi engine runtime euid unavailable");
|
|
298
|
+
let configuredIdentity;
|
|
299
|
+
try {
|
|
300
|
+
configuredIdentity = canonicalFilesystemPath(configured);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
// An inaccessible or dangling configured path remains a distinct identity;
|
|
304
|
+
// it is never accepted as the socket directory below.
|
|
305
|
+
configuredIdentity = path.resolve(configured);
|
|
306
|
+
}
|
|
307
|
+
const configuredKey = createHash("sha256")
|
|
308
|
+
.update(configuredIdentity)
|
|
309
|
+
.digest("hex")
|
|
310
|
+
.slice(0, 32);
|
|
311
|
+
const candidates = [
|
|
312
|
+
configured,
|
|
313
|
+
path.join("/tmp", `kb-${euid}-${configuredKey}`),
|
|
314
|
+
];
|
|
315
|
+
const maximumBytes = process.platform === "linux" ? 107 : 103;
|
|
316
|
+
const socketName = `kibi-${"0".repeat(32)}.sock`;
|
|
254
317
|
for (const candidate of candidates) {
|
|
255
318
|
try {
|
|
319
|
+
try {
|
|
320
|
+
if (lstatSync(candidate).isSymbolicLink())
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
if (error.code !== "ENOENT")
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
256
327
|
mkdirSync(candidate, { recursive: true, mode: 0o700 });
|
|
328
|
+
const directory = lstatSync(candidate);
|
|
329
|
+
if (!directory.isDirectory() ||
|
|
330
|
+
directory.uid !== euid ||
|
|
331
|
+
(directory.mode & 0o077) !== 0)
|
|
332
|
+
continue;
|
|
333
|
+
const canonical = canonicalFilesystemPath(candidate);
|
|
334
|
+
if (Buffer.byteLength(path.join(canonical, socketName), "utf8") >
|
|
335
|
+
maximumBytes)
|
|
336
|
+
continue;
|
|
257
337
|
// XDG_RUNTIME_DIR can exist but be mounted read-only (for example in a
|
|
258
338
|
// constrained container). Probe the directory before using it for the
|
|
259
339
|
// socket, start lock, and pid files so auto-start fails over cleanly.
|
|
260
|
-
const probe = path.join(
|
|
261
|
-
writeFileSync(probe, "", { mode: 0o600 });
|
|
340
|
+
const probe = path.join(canonical, `.kibi-write-${randomUUID()}`);
|
|
341
|
+
writeFileSync(probe, "", { mode: 0o600, flag: "wx" });
|
|
262
342
|
unlinkSync(probe);
|
|
263
|
-
return
|
|
343
|
+
return canonical;
|
|
264
344
|
}
|
|
265
345
|
catch {
|
|
266
346
|
// Try the private system-temp fallback below.
|
|
@@ -318,18 +398,11 @@ export function fsyncJournaledBranchStore(branchPath) {
|
|
|
318
398
|
}
|
|
319
399
|
// implements REQ-core-journaled-engine-persistence
|
|
320
400
|
export function engineSocketPath(workspaceRoot, branch) {
|
|
321
|
-
const canonicalRoot = (
|
|
322
|
-
try {
|
|
323
|
-
return realpathSync(workspaceRoot);
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
return path.resolve(workspaceRoot);
|
|
327
|
-
}
|
|
328
|
-
})();
|
|
401
|
+
const canonicalRoot = canonicalFilesystemPath(workspaceRoot);
|
|
329
402
|
const key = createHash("sha256")
|
|
330
403
|
.update(`${canonicalRoot}\0${branch}`)
|
|
331
|
-
// 128 bits
|
|
332
|
-
//
|
|
404
|
+
// Retain 128 bits of workspace/branch identity; runtimeDirectory budgets
|
|
405
|
+
// the complete pathname rather than shortening this identity.
|
|
333
406
|
.digest("hex")
|
|
334
407
|
.slice(0, 32);
|
|
335
408
|
if (process.platform === "win32") {
|
|
@@ -666,22 +739,36 @@ export class EngineClient {
|
|
|
666
739
|
if (!isValidBranchName(options.branch)) {
|
|
667
740
|
throw new Error(`Invalid Kibi engine branch name: ${options.branch}`);
|
|
668
741
|
}
|
|
669
|
-
|
|
670
|
-
try {
|
|
671
|
-
this.workspaceRoot = realpathSync(resolvedWorkspaceRoot);
|
|
672
|
-
}
|
|
673
|
-
catch {
|
|
674
|
-
this.workspaceRoot = resolvedWorkspaceRoot;
|
|
675
|
-
}
|
|
742
|
+
this.workspaceRoot = canonicalFilesystemPath(options.workspaceRoot);
|
|
676
743
|
this.branch = options.branch;
|
|
677
744
|
this.timeout = options.timeout ?? 120_000;
|
|
678
745
|
this.allowPublicationLock = options.allowPublicationLock ?? false;
|
|
679
746
|
}
|
|
680
747
|
async start(allowSpawn = true) {
|
|
681
748
|
const wasConnected = this.socket !== null && !this.socket.destroyed;
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
749
|
+
for (let attempt = 0;; attempt += 1) {
|
|
750
|
+
await this.connect(allowSpawn);
|
|
751
|
+
if (wasConnected || this.socket === null)
|
|
752
|
+
return;
|
|
753
|
+
try {
|
|
754
|
+
await this.reconcileRuntime();
|
|
755
|
+
await this.reconcileAttachment();
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
// A listener can begin draining after the kernel accepts a connection
|
|
760
|
+
// but before the read-only runtime and attachment handshakes finish.
|
|
761
|
+
// Retry those handshakes once; request() never retries an application
|
|
762
|
+
// RPC.
|
|
763
|
+
if (attempt > 0 ||
|
|
764
|
+
!allowSpawn ||
|
|
765
|
+
!(error instanceof Error) ||
|
|
766
|
+
error.message !== "Kibi engine connection closed" ||
|
|
767
|
+
(this.socket !== null && !this.socket.destroyed))
|
|
768
|
+
throw error;
|
|
769
|
+
this.socket?.destroy();
|
|
770
|
+
this.socket = null;
|
|
771
|
+
}
|
|
685
772
|
}
|
|
686
773
|
}
|
|
687
774
|
async connect(allowSpawn = true) {
|
|
@@ -720,6 +807,22 @@ export class EngineClient {
|
|
|
720
807
|
}
|
|
721
808
|
}
|
|
722
809
|
if (this.socket === null) {
|
|
810
|
+
// A draining daemon removes its listener before saving, but retains
|
|
811
|
+
// its PID until the branch lock is released. Do not start a second
|
|
812
|
+
// writer while that process is still completing shutdown.
|
|
813
|
+
const existingPid = this.getPid();
|
|
814
|
+
if (existingPid > 0) {
|
|
815
|
+
let alive = false;
|
|
816
|
+
try {
|
|
817
|
+
process.kill(existingPid, 0);
|
|
818
|
+
alive = true;
|
|
819
|
+
}
|
|
820
|
+
catch { }
|
|
821
|
+
if (alive) {
|
|
822
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
723
826
|
lock = tryAcquireStartLock(lockPath);
|
|
724
827
|
if (lock === null) {
|
|
725
828
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
@@ -771,7 +874,10 @@ export class EngineClient {
|
|
|
771
874
|
this.attachSocket(this.socket);
|
|
772
875
|
}
|
|
773
876
|
attachSocket(socket) {
|
|
877
|
+
this.inputBuffer = Buffer.alloc(0);
|
|
774
878
|
socket.on("data", (chunk) => {
|
|
879
|
+
if (this.socket !== socket)
|
|
880
|
+
return;
|
|
775
881
|
try {
|
|
776
882
|
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
777
883
|
this.inputBuffer = parseFrames(Buffer.concat([this.inputBuffer, bytes]), (value) => this.handleResponse(value));
|
|
@@ -781,6 +887,8 @@ export class EngineClient {
|
|
|
781
887
|
}
|
|
782
888
|
});
|
|
783
889
|
socket.on("close", () => {
|
|
890
|
+
if (this.socket !== socket)
|
|
891
|
+
return;
|
|
784
892
|
this.socket = null;
|
|
785
893
|
const error = new Error("Kibi engine connection closed");
|
|
786
894
|
for (const pending of this.pending.values()) {
|
|
@@ -789,6 +897,8 @@ export class EngineClient {
|
|
|
789
897
|
this.pending.clear();
|
|
790
898
|
});
|
|
791
899
|
socket.on("error", (error) => {
|
|
900
|
+
if (this.socket !== socket)
|
|
901
|
+
return;
|
|
792
902
|
for (const pending of this.pending.values()) {
|
|
793
903
|
this.settlePending(pending, () => pending.reject(error));
|
|
794
904
|
}
|
|
@@ -850,9 +960,10 @@ export class EngineClient {
|
|
|
850
960
|
id,
|
|
851
961
|
protocolVersion: ENGINE_PROTOCOL_VERSION,
|
|
852
962
|
packageVersions: ENGINE_PACKAGE_VERSIONS,
|
|
963
|
+
prologIdentity: engineSwiplIdentity(),
|
|
853
964
|
workspaceRoot: this.workspaceRoot,
|
|
854
965
|
branch: this.branch,
|
|
855
|
-
}));
|
|
966
|
+
}), ignoreWriteFailure);
|
|
856
967
|
}
|
|
857
968
|
catch (error) {
|
|
858
969
|
const pending = this.pending.get(id);
|
|
@@ -897,6 +1008,11 @@ export class EngineClient {
|
|
|
897
1008
|
return this.query(`rdf_transaction((${normalized.join(", ")}))`);
|
|
898
1009
|
}
|
|
899
1010
|
async queryEntities(input, signal) {
|
|
1011
|
+
// Full entities carry receipt histories; a caller-sized page (up to 100k
|
|
1012
|
+
// rows) is split into bounded engine requests.
|
|
1013
|
+
return queryEntityChunks((chunk) => this.queryEntityChunk(chunk, signal), input);
|
|
1014
|
+
}
|
|
1015
|
+
async queryEntityChunk(input, signal) {
|
|
900
1016
|
const result = await this.command({
|
|
901
1017
|
version: 1,
|
|
902
1018
|
kind: "entities",
|
|
@@ -954,9 +1070,10 @@ export class EngineClient {
|
|
|
954
1070
|
cancelOf: requestId,
|
|
955
1071
|
protocolVersion: ENGINE_PROTOCOL_VERSION,
|
|
956
1072
|
packageVersions: ENGINE_PACKAGE_VERSIONS,
|
|
1073
|
+
prologIdentity: engineSwiplIdentity(),
|
|
957
1074
|
workspaceRoot: this.workspaceRoot,
|
|
958
1075
|
branch: this.branch,
|
|
959
|
-
}));
|
|
1076
|
+
}), ignoreWriteFailure);
|
|
960
1077
|
}
|
|
961
1078
|
async nextSolution() {
|
|
962
1079
|
const result = this.lastResult;
|
|
@@ -996,6 +1113,36 @@ export class EngineClient {
|
|
|
996
1113
|
return null;
|
|
997
1114
|
return parseEngineAttachmentIdentity(status.bindings.JsonString);
|
|
998
1115
|
}
|
|
1116
|
+
async daemonRuntimeMatches() {
|
|
1117
|
+
const reply = await this.request({
|
|
1118
|
+
method: "handshake",
|
|
1119
|
+
});
|
|
1120
|
+
return reply?.prologIdentity === engineSwiplIdentity();
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* A live daemon keeps the SWI-Prolog it was started with. When this client
|
|
1124
|
+
* resolves a different executable or version (KIBI_SWIPL changed, the
|
|
1125
|
+
* bundled package was upgraded, a pre-handshake daemon), the daemon is
|
|
1126
|
+
* stopped and replaced; it is never reused.
|
|
1127
|
+
*/
|
|
1128
|
+
// implements REQ-prolog-daemon-runtime-identity
|
|
1129
|
+
async reconcileRuntime() {
|
|
1130
|
+
if (this.replacingStaleDaemon || this.socket === null)
|
|
1131
|
+
return;
|
|
1132
|
+
if (await this.daemonRuntimeMatches())
|
|
1133
|
+
return;
|
|
1134
|
+
this.replacingStaleDaemon = true;
|
|
1135
|
+
try {
|
|
1136
|
+
await this.shutdownConnectedDaemon();
|
|
1137
|
+
await this.connect(true);
|
|
1138
|
+
if (!(await this.daemonRuntimeMatches())) {
|
|
1139
|
+
throw new Error(`Kibi engine is running a different SWI-Prolog than this client resolved (${engineSwiplIdentity()}); run 'kibi engine stop' and retry`);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
finally {
|
|
1143
|
+
this.replacingStaleDaemon = false;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
999
1146
|
async reconcileAttachment() {
|
|
1000
1147
|
if (this.replacingStaleDaemon || this.socket === null)
|
|
1001
1148
|
return;
|
|
@@ -1020,26 +1167,40 @@ export class EngineClient {
|
|
|
1020
1167
|
}
|
|
1021
1168
|
async shutdownConnectedDaemon() {
|
|
1022
1169
|
const socketPath = engineSocketPath(this.workspaceRoot, this.branch);
|
|
1170
|
+
let requestError;
|
|
1023
1171
|
if (!this.isRunning()) {
|
|
1024
1172
|
if (existsSync(socketPath)) {
|
|
1025
1173
|
throw new Error(`Kibi engine socket remains present but is not reachable: ${socketPath}`);
|
|
1026
1174
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1175
|
+
const existingPid = this.getPid();
|
|
1176
|
+
if (existingPid <= 0)
|
|
1177
|
+
return;
|
|
1178
|
+
try {
|
|
1179
|
+
process.kill(existingPid, 0);
|
|
1180
|
+
}
|
|
1181
|
+
catch {
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
// An already-draining daemon can have no listener while its durability
|
|
1185
|
+
// save still owns the store. A stop caller must wait for that handoff.
|
|
1032
1186
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1187
|
+
else {
|
|
1188
|
+
try {
|
|
1189
|
+
await this.request({ method: "stop" });
|
|
1190
|
+
}
|
|
1191
|
+
catch (error) {
|
|
1192
|
+
requestError = error;
|
|
1193
|
+
}
|
|
1194
|
+
this.socket?.destroy();
|
|
1195
|
+
this.socket = null;
|
|
1035
1196
|
}
|
|
1036
|
-
this.
|
|
1037
|
-
this.socket = null;
|
|
1197
|
+
const pidPath = enginePidPath(this.workspaceRoot, this.branch);
|
|
1038
1198
|
const deadline = Date.now() + 5_000;
|
|
1039
|
-
while (existsSync(socketPath)
|
|
1199
|
+
while ((existsSync(socketPath) || existsSync(pidPath)) &&
|
|
1200
|
+
Date.now() < deadline) {
|
|
1040
1201
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1041
1202
|
}
|
|
1042
|
-
if (existsSync(socketPath)) {
|
|
1203
|
+
if (existsSync(socketPath) || existsSync(pidPath)) {
|
|
1043
1204
|
const detail = requestError instanceof Error ? `: ${requestError.message}` : "";
|
|
1044
1205
|
throw new Error(`Kibi engine did not stop within 5 seconds${detail}`);
|
|
1045
1206
|
}
|
|
@@ -1065,11 +1226,8 @@ export class EngineClient {
|
|
|
1065
1226
|
throw new Error("Unable to start Kibi engine for stop request");
|
|
1066
1227
|
}
|
|
1067
1228
|
}
|
|
1068
|
-
if (!this.isRunning()) {
|
|
1069
|
-
|
|
1070
|
-
throw new Error(`Kibi engine socket remains present but is not reachable: ${socketPath}`);
|
|
1071
|
-
}
|
|
1072
|
-
return;
|
|
1229
|
+
if (!this.isRunning() && existsSync(socketPath)) {
|
|
1230
|
+
throw new Error(`Kibi engine socket remains present but is not reachable: ${socketPath}`);
|
|
1073
1231
|
}
|
|
1074
1232
|
await this.shutdownConnectedDaemon();
|
|
1075
1233
|
}
|
|
@@ -1286,37 +1444,116 @@ function recoverInterruptedGeneration(branchPath) {
|
|
|
1286
1444
|
}
|
|
1287
1445
|
}
|
|
1288
1446
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1447
|
+
function isEngineLifecycleRequest(request) {
|
|
1448
|
+
if (request.method === "stop" ||
|
|
1449
|
+
request.method === "cancel" ||
|
|
1450
|
+
request.method === "handshake")
|
|
1451
|
+
return true;
|
|
1452
|
+
const kind = request.command?.kind;
|
|
1453
|
+
return (request.method === "command" &&
|
|
1454
|
+
(kind === "stop" || kind === "cancel" || kind === "lifecycle"));
|
|
1455
|
+
}
|
|
1456
|
+
const ENGINE_PRELOADED_MODULES = [
|
|
1457
|
+
["status.pl", "status"],
|
|
1458
|
+
["discovery.pl", "discovery"],
|
|
1459
|
+
];
|
|
1460
|
+
const USE_MODULE_GOAL = /^use_module\('(?:[^']|'')*'\)$/u;
|
|
1461
|
+
/**
|
|
1462
|
+
* Owns the daemon's interactive SWI session: the attached branch store plus
|
|
1463
|
+
* every module loaded into it. An overflow or timeout terminates the SWI
|
|
1464
|
+
* child; the next request recycles the session (restart, reattach, reload
|
|
1465
|
+
* modules) instead of letting the port degrade into isolated one-shot
|
|
1466
|
+
* processes that have lost the attachment and module state.
|
|
1467
|
+
*/
|
|
1468
|
+
// implements REQ-core-journaled-engine-persistence
|
|
1469
|
+
export class EngineSession {
|
|
1470
|
+
prolog;
|
|
1471
|
+
branchPath;
|
|
1472
|
+
modules = [];
|
|
1473
|
+
constructor(prolog, branchPath) {
|
|
1474
|
+
this.prolog = prolog;
|
|
1475
|
+
this.branchPath = branchPath;
|
|
1476
|
+
const coreModuleDir = path.dirname(resolveKbPlPath());
|
|
1477
|
+
for (const [fileName, label] of ENGINE_PRELOADED_MODULES) {
|
|
1478
|
+
const modulePath = path.join(coreModuleDir, fileName);
|
|
1479
|
+
this.modules.push({
|
|
1480
|
+
goal: `use_module('${quoteProlog(modulePath.replaceAll("\\", "/"))}')`,
|
|
1481
|
+
label,
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1293
1484
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
["status.pl", "status"],
|
|
1312
|
-
["discovery.pl", "discovery"],
|
|
1313
|
-
]) {
|
|
1314
|
-
const modulePath = path.join(coreModuleDir, fileName);
|
|
1315
|
-
const loaded = await prolog.query(`use_module('${quoteProlog(modulePath.replaceAll("\\", "/"))}')`);
|
|
1316
|
-
if (!loaded.success) {
|
|
1317
|
-
throw new Error(loaded.error ?? `Failed to load Kibi ${label} module`);
|
|
1485
|
+
async boot() {
|
|
1486
|
+
await this.prolog.start();
|
|
1487
|
+
const attached = await this.prolog.query(`kb_attach('${quoteProlog(this.branchPath)}')`);
|
|
1488
|
+
if (!attached.success) {
|
|
1489
|
+
// The daemon hosts the branch store for every later request, so a
|
|
1490
|
+
// store-locked startup with a provably dead holder is auto-healed here
|
|
1491
|
+
// exactly like the CLI runtime path; live holders abort the startup
|
|
1492
|
+
// with the holder identity surfaced.
|
|
1493
|
+
const recovered = await retryAttachAfterBreakingStaleLock(this.prolog, this.branchPath, attached);
|
|
1494
|
+
if (!recovered.success)
|
|
1495
|
+
throw new Error(recovered.error ?? "Failed to attach branch KB");
|
|
1496
|
+
}
|
|
1497
|
+
for (const { goal, label } of this.modules) {
|
|
1498
|
+
const loaded = await this.prolog.query(goal);
|
|
1499
|
+
if (!loaded.success) {
|
|
1500
|
+
throw new Error(loaded.error ?? `Failed to load Kibi ${label} module`);
|
|
1501
|
+
}
|
|
1318
1502
|
}
|
|
1319
1503
|
}
|
|
1504
|
+
/** Remember a successful client module load so a recycle restores it. */
|
|
1505
|
+
recordModuleLoad(goal, result) {
|
|
1506
|
+
const normalized = goal.trim().replace(/\.+\s*$/u, "");
|
|
1507
|
+
if (result.success &&
|
|
1508
|
+
USE_MODULE_GOAL.test(normalized) &&
|
|
1509
|
+
!this.modules.some((module) => module.goal === normalized)) {
|
|
1510
|
+
this.modules.push({ goal: normalized, label: "client-loaded" });
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
needsRecycle() {
|
|
1514
|
+
return this.prolog.needsRestart();
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Restart a lost interactive session. Returns true when a recycle ran. A
|
|
1518
|
+
* failed recycle leaves the process stopped so the next request retries.
|
|
1519
|
+
*/
|
|
1520
|
+
async ensureLive() {
|
|
1521
|
+
if (!this.prolog.needsRestart())
|
|
1522
|
+
return false;
|
|
1523
|
+
console.error("[KIBI] engine Prolog session was lost (output overflow, timeout, or crash); restarting and reattaching the branch store");
|
|
1524
|
+
try {
|
|
1525
|
+
await this.boot();
|
|
1526
|
+
}
|
|
1527
|
+
catch (error) {
|
|
1528
|
+
await this.prolog.terminate().catch(() => undefined);
|
|
1529
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1530
|
+
throw new Error(`Kibi engine failed to recycle its Prolog session: ${message}`);
|
|
1531
|
+
}
|
|
1532
|
+
return true;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
// implements REQ-core-journaled-engine-persistence
|
|
1536
|
+
export async function runEngineDaemon(requestedOptions) {
|
|
1537
|
+
if (!isValidBranchName(requestedOptions.branch)) {
|
|
1538
|
+
throw new Error(`Invalid Kibi engine branch name: ${requestedOptions.branch}`);
|
|
1539
|
+
}
|
|
1540
|
+
const options = {
|
|
1541
|
+
...requestedOptions,
|
|
1542
|
+
workspaceRoot: canonicalFilesystemPath(requestedOptions.workspaceRoot),
|
|
1543
|
+
};
|
|
1544
|
+
const branchPath = ensureBranchStoreManifest(options.workspaceRoot, options.branch);
|
|
1545
|
+
await ensureJournaledBranchStoreAsync(branchPath);
|
|
1546
|
+
const prolog = new PrologProcess({
|
|
1547
|
+
timeout: 120_000,
|
|
1548
|
+
oneShot: false,
|
|
1549
|
+
...(options.maxOutputBytes === undefined
|
|
1550
|
+
? {}
|
|
1551
|
+
: { maxOutputBytes: options.maxOutputBytes }),
|
|
1552
|
+
});
|
|
1553
|
+
const session = new EngineSession(prolog, branchPath);
|
|
1554
|
+
await session.boot();
|
|
1555
|
+
let attachedIdentity = readEngineAttachmentIdentity(branchPath);
|
|
1556
|
+
const serverPrologIdentity = engineSwiplIdentity();
|
|
1320
1557
|
mkdirSync(path.dirname(options.socketPath), { recursive: true, mode: 0o700 });
|
|
1321
1558
|
if (existsSync(options.socketPath)) {
|
|
1322
1559
|
const isLivePeerSignal = (error) => {
|
|
@@ -1387,6 +1624,10 @@ export async function runEngineDaemon(options) {
|
|
|
1387
1624
|
let freshnessCache = null;
|
|
1388
1625
|
const clients = new Set();
|
|
1389
1626
|
let shuttingDown = false;
|
|
1627
|
+
let finishShutdown;
|
|
1628
|
+
const shutdownFinished = new Promise((resolve) => {
|
|
1629
|
+
finishShutdown = resolve;
|
|
1630
|
+
});
|
|
1390
1631
|
const exactBranchStatus = (result) => {
|
|
1391
1632
|
if (!result.success || typeof result.bindings.JsonString !== "string") {
|
|
1392
1633
|
return result;
|
|
@@ -1434,13 +1675,14 @@ export async function runEngineDaemon(options) {
|
|
|
1434
1675
|
}, engineWorkspaceWatchdogMs());
|
|
1435
1676
|
workspaceWatchdog.unref();
|
|
1436
1677
|
const scheduleIdleExit = () => {
|
|
1437
|
-
if (activeClients > 0)
|
|
1678
|
+
if (shuttingDown || activeClients > 0)
|
|
1438
1679
|
return;
|
|
1439
1680
|
if (!idleCompactionQueued) {
|
|
1440
1681
|
idleCompactionQueued = true;
|
|
1441
1682
|
queue = queue
|
|
1442
1683
|
.then(async () => {
|
|
1443
|
-
|
|
1684
|
+
// A lost session is recycled by the next request, not by idle work.
|
|
1685
|
+
if (activeClients === 0 && !session.needsRecycle()) {
|
|
1444
1686
|
await prolog.query("kb_storage_compact_if_needed");
|
|
1445
1687
|
}
|
|
1446
1688
|
})
|
|
@@ -1465,11 +1707,26 @@ export async function runEngineDaemon(options) {
|
|
|
1465
1707
|
request.packageVersions !== expectedPackages) {
|
|
1466
1708
|
throw new Error(`Kibi engine package-version mismatch: client=${request.packageVersions ?? "missing"}, server=${expectedPackages}`);
|
|
1467
1709
|
}
|
|
1468
|
-
if (
|
|
1469
|
-
|
|
1710
|
+
if (canonicalFilesystemPath(request.workspaceRoot ?? "") !==
|
|
1711
|
+
options.workspaceRoot ||
|
|
1470
1712
|
request.branch !== options.branch) {
|
|
1471
1713
|
throw new Error("Kibi engine workspace identity mismatch");
|
|
1472
1714
|
}
|
|
1715
|
+
// A client that resolved a different Prolog must not be served by this
|
|
1716
|
+
// daemon. Handshake reports the identity and stop must still work so the
|
|
1717
|
+
// client can replace the daemon.
|
|
1718
|
+
if (request.method !== "handshake" &&
|
|
1719
|
+
request.method !== "stop" &&
|
|
1720
|
+
request.prologIdentity !== serverPrologIdentity) {
|
|
1721
|
+
throw new Error(`Kibi engine SWI-Prolog mismatch: client=${request.prologIdentity ?? "missing"}, server=${serverPrologIdentity}`);
|
|
1722
|
+
}
|
|
1723
|
+
if (!isEngineLifecycleRequest(request) && (await session.ensureLive())) {
|
|
1724
|
+
// A recycled session re-read the store; nothing cached from the lost
|
|
1725
|
+
// session may be served, and the attachment identity may have moved.
|
|
1726
|
+
queryCache.clear();
|
|
1727
|
+
freshnessCache = null;
|
|
1728
|
+
attachedIdentity = readEngineAttachmentIdentity(branchPath);
|
|
1729
|
+
}
|
|
1473
1730
|
switch (request.method) {
|
|
1474
1731
|
case "entities": {
|
|
1475
1732
|
const limit = request.limit ?? 100;
|
|
@@ -1653,8 +1910,15 @@ export async function runEngineDaemon(options) {
|
|
|
1653
1910
|
}
|
|
1654
1911
|
}
|
|
1655
1912
|
case "kbStatus": {
|
|
1913
|
+
const freshnessCacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance.now() : null;
|
|
1656
1914
|
if (freshnessCache !== null &&
|
|
1657
1915
|
Date.now() - freshnessCache.capturedAt <= ENGINE_FRESHNESS_CACHE_MS) {
|
|
1916
|
+
if (freshnessCacheLookupStartedAt !== null) {
|
|
1917
|
+
writePerformanceTraceEvent({
|
|
1918
|
+
kind: "engine-freshness-cache-hit",
|
|
1919
|
+
durationMs: performance.now() - freshnessCacheLookupStartedAt,
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1658
1922
|
return freshnessCache.result;
|
|
1659
1923
|
}
|
|
1660
1924
|
const result = exactBranchStatus(await prolog.query("status:kb_status_json(JsonString)"));
|
|
@@ -1674,11 +1938,20 @@ export async function runEngineDaemon(options) {
|
|
|
1674
1938
|
freshnessCache = null;
|
|
1675
1939
|
}
|
|
1676
1940
|
if (cacheableEngineGoal(request.goal)) {
|
|
1941
|
+
const queryCacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance.now() : null;
|
|
1677
1942
|
const cached = queryCache.get(request.goal);
|
|
1678
|
-
if (cached !== undefined)
|
|
1943
|
+
if (cached !== undefined) {
|
|
1944
|
+
if (queryCacheLookupStartedAt !== null) {
|
|
1945
|
+
writePerformanceTraceEvent({
|
|
1946
|
+
kind: "engine-query-cache-hit",
|
|
1947
|
+
durationMs: performance.now() - queryCacheLookupStartedAt,
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1679
1950
|
return cached;
|
|
1951
|
+
}
|
|
1680
1952
|
}
|
|
1681
1953
|
const result = await prolog.query(request.goal);
|
|
1954
|
+
session.recordModuleLoad(request.goal, result);
|
|
1682
1955
|
if (result.success && mutatingEngineGoal(request.goal)) {
|
|
1683
1956
|
fsyncJournaledBranchStore(branchPath);
|
|
1684
1957
|
}
|
|
@@ -1715,6 +1988,8 @@ export async function runEngineDaemon(options) {
|
|
|
1715
1988
|
if (typeof request.targetDirectory !== "string")
|
|
1716
1989
|
throw new Error("export.targetDirectory is required");
|
|
1717
1990
|
return prolog.query(`kb_storage_export('${quoteProlog(request.targetDirectory)}')`);
|
|
1991
|
+
case "handshake":
|
|
1992
|
+
return { prologIdentity: serverPrologIdentity };
|
|
1718
1993
|
case "stop":
|
|
1719
1994
|
setImmediate(() => void shutdown());
|
|
1720
1995
|
return { stopped: true };
|
|
@@ -1741,7 +2016,13 @@ export async function runEngineDaemon(options) {
|
|
|
1741
2016
|
if (idleTimer)
|
|
1742
2017
|
clearTimeout(idleTimer);
|
|
1743
2018
|
clearInterval(workspaceWatchdog);
|
|
1744
|
-
|
|
2019
|
+
// Stop accepting clients before the first asynchronous durability step.
|
|
2020
|
+
// Keep the PID published until Prolog releases the branch-store lock.
|
|
2021
|
+
server.close();
|
|
2022
|
+
await queue.catch(() => undefined);
|
|
2023
|
+
const saved = session.needsRecycle()
|
|
2024
|
+
? null
|
|
2025
|
+
: await prolog.query("kb_save").catch(() => null);
|
|
1745
2026
|
if (saved?.success) {
|
|
1746
2027
|
try {
|
|
1747
2028
|
fsyncJournaledBranchStore(branchPath);
|
|
@@ -1754,7 +2035,6 @@ export async function runEngineDaemon(options) {
|
|
|
1754
2035
|
await prolog.terminate().catch(() => undefined);
|
|
1755
2036
|
for (const client of clients)
|
|
1756
2037
|
client.destroy();
|
|
1757
|
-
server.close();
|
|
1758
2038
|
// A close callback can remain pending on older Node releases when a
|
|
1759
2039
|
// client disconnects during `server.close()`. The daemon has already
|
|
1760
2040
|
// removed its socket/pid and has no useful work left, so do not keep the
|
|
@@ -1775,6 +2055,7 @@ export async function runEngineDaemon(options) {
|
|
|
1775
2055
|
// gone, force the final event-loop exit rather than retaining a stale
|
|
1776
2056
|
// detached Node process on platform-specific server bookkeeping.
|
|
1777
2057
|
setTimeout(() => process.exit(0), 50).unref();
|
|
2058
|
+
finishShutdown();
|
|
1778
2059
|
};
|
|
1779
2060
|
// Detached engines must cross the same durability boundary when a service
|
|
1780
2061
|
// manager or a test harness terminates them as they do for an RPC stop.
|
|
@@ -1783,6 +2064,12 @@ export async function runEngineDaemon(options) {
|
|
|
1783
2064
|
process.once("SIGTERM", requestSignalShutdown);
|
|
1784
2065
|
process.once("SIGINT", requestSignalShutdown);
|
|
1785
2066
|
server.on("connection", (socket) => {
|
|
2067
|
+
// A peer may disappear while the daemon refuses or drains its socket.
|
|
2068
|
+
socket.on("error", () => undefined);
|
|
2069
|
+
if (shuttingDown) {
|
|
2070
|
+
socket.destroy();
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
1786
2073
|
clients.add(socket);
|
|
1787
2074
|
activeClients += 1;
|
|
1788
2075
|
// Request ids are per-connection. Cancel marks must not be daemon-global
|
|
@@ -1794,6 +2081,15 @@ export async function runEngineDaemon(options) {
|
|
|
1794
2081
|
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
1795
2082
|
buffer = parseFrames(Buffer.concat([buffer, bytes]), (value) => {
|
|
1796
2083
|
const request = value;
|
|
2084
|
+
if (shuttingDown) {
|
|
2085
|
+
writeSocketFrame(socket, {
|
|
2086
|
+
id: request.id,
|
|
2087
|
+
ok: false,
|
|
2088
|
+
error: "Kibi engine is shutting down; request was not executed",
|
|
2089
|
+
serverPackageVersions: ENGINE_PACKAGE_VERSIONS,
|
|
2090
|
+
});
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
1797
2093
|
if (request.method === "cancel" &&
|
|
1798
2094
|
typeof request.cancelOf === "number") {
|
|
1799
2095
|
cancelledRequests.add(request.cancelOf);
|
|
@@ -1862,4 +2158,5 @@ export async function runEngineDaemon(options) {
|
|
|
1862
2158
|
await new Promise((resolve) => {
|
|
1863
2159
|
server.once("close", resolve);
|
|
1864
2160
|
});
|
|
2161
|
+
await shutdownFinished;
|
|
1865
2162
|
}
|