@intentius/chant 0.57.0 → 0.59.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/audit/catalog.d.ts +51 -1
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +36 -1
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +26 -9
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/lineage.d.ts +10 -0
- package/dist/audit/lineage.d.ts.map +1 -0
- package/dist/audit/prior-art.d.ts +385 -0
- package/dist/audit/prior-art.d.ts.map +1 -0
- package/dist/audit/proof.d.ts +16 -0
- package/dist/audit/proof.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +8 -1
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts +3 -0
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +15 -0
- package/dist/audit/secrets.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +46 -0
- package/dist/audit/terraform-state.d.ts.map +1 -0
- package/dist/build.d.ts +17 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/claimed-fields.d.ts +161 -0
- package/dist/claimed-fields.d.ts.map +1 -0
- package/dist/cli/build-params-cli.d.ts +2 -2
- package/dist/cli/command-group.d.ts +14 -0
- package/dist/cli/command-group.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +17 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/carve-bridge.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts +13 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +2 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +82 -16
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +49 -42
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts +12 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts +16 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resources/context.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +22 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +40 -9
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
- package/dist/components/auto-release.d.ts +10 -20
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +4 -4
- package/dist/components/cli-support.d.ts +24 -22
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +21 -6
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver-output.d.ts.map +1 -1
- package/dist/components/driver.d.ts +73 -37
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/manifest-persistence.d.ts +1 -9
- package/dist/components/manifest-persistence.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +6 -6
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +10 -22
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts +2 -2
- package/dist/deep-observation.d.ts +17 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/detectLexicon.d.ts +13 -0
- package/dist/detectLexicon.d.ts.map +1 -1
- package/dist/discovery/files.d.ts +4 -4
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +33 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/config-wire.d.ts +2 -2
- package/dist/fold/foldable-helpers.d.ts +5 -1
- package/dist/fold/foldable-helpers.d.ts.map +1 -1
- package/dist/fold-provenance.d.ts +236 -0
- package/dist/fold-provenance.d.ts.map +1 -0
- package/dist/graph-ops.d.ts +1 -1
- package/dist/held-elsewhere.d.ts +85 -0
- package/dist/held-elsewhere.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts +3 -3
- package/dist/lexicon-config.d.ts +4 -4
- package/dist/lexicon-plugin-helpers.d.ts +1 -1
- package/dist/lexicon.d.ts +273 -16
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +13 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/deep-diff.d.ts +133 -6
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +79 -3
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +2 -2
- package/dist/lifecycle/run-ledger.d.ts +53 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +3 -3
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lint/config.d.ts +81 -2
- package/dist/lint/config.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +55 -2
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
- package/dist/lint/rules/comp/index.d.ts +1 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +16 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
- package/dist/lint/rules/op/support.d.ts +12 -0
- package/dist/lint/rules/op/support.d.ts.map +1 -0
- package/dist/lint/suppressions.d.ts +108 -0
- package/dist/lint/suppressions.d.ts.map +1 -0
- package/dist/live-endpoint.d.ts +1 -1
- package/dist/op/activities/activity-contracts.d.ts +54 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -0
- package/dist/op/activities/apply.d.ts +262 -0
- package/dist/op/activities/apply.d.ts.map +1 -0
- package/dist/op/activities/build.d.ts +13 -0
- package/dist/op/activities/build.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +110 -0
- package/dist/op/activities/converge.d.ts.map +1 -0
- package/dist/op/activities/env-teardown.d.ts +76 -0
- package/dist/op/activities/env-teardown.d.ts.map +1 -0
- package/dist/op/activities/guard-validate.d.ts +64 -0
- package/dist/op/activities/guard-validate.d.ts.map +1 -0
- package/dist/op/activities/http-check.d.ts +34 -0
- package/dist/op/activities/http-check.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +31 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
- package/dist/op/activities/lifecycle.d.ts +42 -0
- package/dist/op/activities/lifecycle.d.ts.map +1 -0
- package/dist/op/activities/pipeline-audit.d.ts +84 -0
- package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
- package/dist/op/activities/policy.d.ts +17 -0
- package/dist/op/activities/policy.d.ts.map +1 -0
- package/dist/op/activities/reconcile.d.ts +149 -0
- package/dist/op/activities/reconcile.d.ts.map +1 -0
- package/dist/op/activities/shell.d.ts +13 -0
- package/dist/op/activities/shell.d.ts.map +1 -0
- package/dist/op/activities/teardown.d.ts +10 -0
- package/dist/op/activities/teardown.d.ts.map +1 -0
- package/dist/op/activities/util.d.ts +7 -0
- package/dist/op/activities/util.d.ts.map +1 -0
- package/dist/op/activities/wait.d.ts +28 -0
- package/dist/op/activities/wait.d.ts.map +1 -0
- package/dist/op/activities/workflow-audit.d.ts +92 -0
- package/dist/op/activities/workflow-audit.d.ts.map +1 -0
- package/dist/op/activity-contract-registry.d.ts +76 -0
- package/dist/op/activity-contract-registry.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +25 -18
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-failure.d.ts +28 -0
- package/dist/op/activity-failure.d.ts.map +1 -0
- package/dist/op/activity-profiles.d.ts +118 -0
- package/dist/op/activity-profiles.d.ts.map +1 -0
- package/dist/op/activity-registry.d.ts +22 -38
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/activity-runtime.d.ts +3 -14
- package/dist/op/activity-runtime.d.ts.map +1 -1
- package/dist/op/builders.d.ts +85 -30
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/change-signal.d.ts +91 -0
- package/dist/op/change-signal.d.ts.map +1 -0
- package/dist/op/composites/apply-op.d.ts +118 -0
- package/dist/op/composites/apply-op.d.ts.map +1 -0
- package/dist/op/composites/converge-op.d.ts +120 -0
- package/dist/op/composites/converge-op.d.ts.map +1 -0
- package/dist/op/composites/index.d.ts +26 -0
- package/dist/op/composites/index.d.ts.map +1 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
- package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
- package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
- package/dist/op/composites/reconcile-op.d.ts +57 -0
- package/dist/op/composites/reconcile-op.d.ts.map +1 -0
- package/dist/op/composites/watch-op.d.ts +60 -0
- package/dist/op/composites/watch-op.d.ts.map +1 -0
- package/dist/op/composites/workflow-audit-op.d.ts +57 -0
- package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
- package/dist/op/converge-rule.d.ts +12 -10
- package/dist/op/converge-rule.d.ts.map +1 -1
- package/dist/op/cron.d.ts +51 -0
- package/dist/op/cron.d.ts.map +1 -0
- package/dist/op/discover.d.ts +9 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/duration.d.ts +11 -0
- package/dist/op/duration.d.ts.map +1 -0
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/gate-name.d.ts +40 -0
- package/dist/op/gate-name.d.ts.map +1 -0
- package/dist/op/gate-summary.d.ts +43 -0
- package/dist/op/gate-summary.d.ts.map +1 -0
- package/dist/op/gate.d.ts +90 -0
- package/dist/op/gate.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +9 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -1
- package/dist/op/index.d.ts +29 -9
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +114 -28
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +11 -1
- package/dist/op/local-output.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +185 -0
- package/dist/op/op-ir.d.ts.map +1 -0
- package/dist/op/op-verb-class.d.ts +10 -2
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts +93 -5
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/receipt-store.d.ts +1 -1
- package/dist/op/receipt-store.d.ts.map +1 -1
- package/dist/op/resource.d.ts +15 -1
- package/dist/op/resource.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +200 -0
- package/dist/op/runtime.d.ts.map +1 -0
- package/dist/op/runtimes/local.d.ts +29 -0
- package/dist/op/runtimes/local.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +15 -13
- package/dist/op/step-output-ref.d.ts.map +1 -1
- package/dist/op/types.d.ts +96 -29
- package/dist/op/types.d.ts.map +1 -1
- package/dist/ownership.d.ts +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/serializer-walker.d.ts.map +1 -1
- package/dist/terraform/bridge.d.ts +26 -7
- package/dist/terraform/bridge.d.ts.map +1 -1
- package/dist/terraform/carve-provider.d.ts +28 -2
- package/dist/terraform/carve-provider.d.ts.map +1 -1
- package/dist/terraform/data-source-shape.d.ts +77 -0
- package/dist/terraform/data-source-shape.d.ts.map +1 -0
- package/dist/terraform/graph.d.ts.map +1 -1
- package/dist/terraform/providers/kubernetes.d.ts +5 -0
- package/dist/terraform/providers/kubernetes.d.ts.map +1 -1
- package/dist/terraform/tier-map.d.ts +16 -4
- package/dist/terraform/tier-map.d.ts.map +1 -1
- package/dist/terraform/types.d.ts +8 -0
- package/dist/terraform/types.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/audit/catalog.test.ts +79 -3
- package/src/audit/catalog.ts +111 -13
- package/src/audit/core.test.ts +26 -0
- package/src/audit/core.ts +84 -18
- package/src/audit/discover.test.ts +39 -0
- package/src/audit/discover.ts +202 -18
- package/src/audit/lineage.ts +100 -0
- package/src/audit/prior-art.ts +153 -0
- package/src/audit/proof.test.ts +67 -0
- package/src/audit/proof.ts +70 -0
- package/src/audit/report-html.ts +9 -4
- package/src/audit/report-model.ts +16 -2
- package/src/audit/report.test.ts +33 -0
- package/src/audit/report.ts +5 -3
- package/src/audit/rules-doc.test.ts +34 -2
- package/src/audit/rules-doc.ts +82 -9
- package/src/audit/secrets.ts +15 -3
- package/src/audit/terraform-state.test.ts +144 -0
- package/src/audit/terraform-state.ts +131 -0
- package/src/build.ts +31 -1
- package/src/claimed-fields.test.ts +145 -0
- package/src/claimed-fields.ts +208 -0
- package/src/cli/build-params-cli.ts +2 -2
- package/src/cli/command-group.ts +14 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
- package/src/cli/commands/audit.test.ts +191 -11
- package/src/cli/commands/audit.ts +78 -28
- package/src/cli/commands/build.test.ts +330 -9
- package/src/cli/commands/build.ts +170 -24
- package/src/cli/commands/carve-bridge.test.ts +237 -14
- package/src/cli/commands/carve-bridge.ts +21 -17
- package/src/cli/commands/carve-emit-k8s.test.ts +17 -10
- package/src/cli/commands/check-lexicon-docs.ts +1 -1
- package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
- package/src/cli/commands/check-lexicon-examples.ts +42 -10
- package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
- package/src/cli/commands/init-lexicon.test.ts +97 -2
- package/src/cli/commands/init-lexicon.ts +4 -2
- package/src/cli/commands/init.test.ts +35 -1
- package/src/cli/commands/init.ts +14 -3
- package/src/cli/commands/lint.test.ts +194 -1
- package/src/cli/commands/lint.ts +205 -7
- package/src/cli/commands/onboard.test.ts +75 -0
- package/src/cli/commands/onboard.ts +54 -23
- package/src/cli/handlers/build.test.ts +81 -1
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/graph.test.ts +3 -3
- package/src/cli/handlers/lifecycle.test.ts +195 -12
- package/src/cli/handlers/lifecycle.ts +123 -6
- package/src/cli/handlers/misc.ts +1 -1
- package/src/cli/handlers/operator.test.ts +183 -11
- package/src/cli/handlers/operator.ts +341 -55
- package/src/cli/handlers/run.test.ts +568 -1295
- package/src/cli/handlers/run.ts +446 -912
- package/src/cli/main.test.ts +22 -16
- package/src/cli/main.ts +66 -33
- package/src/cli/mcp/op-tools.ts +147 -88
- package/src/cli/mcp/resource-handlers.ts +10 -37
- package/src/cli/mcp/resources/context.ts +11 -12
- package/src/cli/mcp/server.test.ts +25 -27
- package/src/cli/mcp/server.ts +2 -2
- package/src/cli/plugins.test.ts +68 -2
- package/src/cli/plugins.ts +39 -0
- package/src/cli/registry.ts +40 -9
- package/src/codegen/docs.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.ts +1 -1
- package/src/components/README.md +4 -3
- package/src/components/__fixtures__/neo4j-fanout.json +1 -1
- package/src/components/auto-release.test.ts +4 -20
- package/src/components/auto-release.ts +17 -42
- package/src/components/capability.ts +4 -4
- package/src/components/cli-support.test.ts +28 -12
- package/src/components/cli-support.ts +60 -47
- package/src/components/component-schema.test.ts +18 -2
- package/src/components/component.schema.json +18 -5
- package/src/components/component.test.ts +2 -2
- package/src/components/component.ts +27 -7
- package/src/components/config-defaults.test.ts +2 -2
- package/src/components/driver-output.ts +14 -2
- package/src/components/driver.test.ts +109 -20
- package/src/components/driver.ts +201 -84
- package/src/components/index.ts +1 -6
- package/src/components/manifest-persistence.test.ts +2 -20
- package/src/components/manifest-persistence.ts +4 -24
- package/src/components/pilots/neo4j-fanout.pilot.ts +3 -3
- package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
- package/src/components/run-progress.ts +6 -6
- package/src/components/verbs/run-agent.test.ts +19 -0
- package/src/components/verbs/run-agent.ts +12 -24
- package/src/components/verbs/verify.ts +2 -2
- package/src/deep-observation.test.ts +75 -0
- package/src/deep-observation.ts +48 -0
- package/src/detectLexicon.ts +18 -1
- package/src/discovery/collect.test.ts +3 -3
- package/src/discovery/files.ts +4 -4
- package/src/discovery/fold-import.test.ts +1 -1
- package/src/discovery/fold-import.ts +105 -2
- package/src/discovery/param-deps.test.ts +112 -1
- package/src/discovery/param-deps.ts +193 -0
- package/src/discovery/sandbox/bundle.ts +15 -1
- package/src/discovery/sandbox/config-wire.test.ts +2 -2
- package/src/discovery/sandbox/config-wire.ts +2 -2
- package/src/fold/fold.test.ts +56 -0
- package/src/fold/foldable-helpers.ts +82 -2
- package/src/fold-provenance-exclusion.test.ts +186 -0
- package/src/fold-provenance.test.ts +495 -0
- package/src/fold-provenance.ts +441 -0
- package/src/graph-ops.test.ts +3 -3
- package/src/graph-ops.ts +1 -1
- package/src/held-elsewhere.test.ts +102 -0
- package/src/held-elsewhere.ts +114 -0
- package/src/index.ts +3 -0
- package/src/kubectl-context.ts +3 -3
- package/src/lexicon-config.test.ts +5 -5
- package/src/lexicon-config.ts +4 -4
- package/src/lexicon-plugin-helpers.ts +1 -1
- package/src/lexicon.ts +296 -16
- package/src/lifecycle/change-set.test.ts +49 -0
- package/src/lifecycle/change-set.ts +67 -0
- package/src/lifecycle/converge-ledger.ts +1 -1
- package/src/lifecycle/deep-diff.test.ts +267 -14
- package/src/lifecycle/deep-diff.ts +217 -14
- package/src/lifecycle/deep-observe.test.ts +3 -1
- package/src/lifecycle/deep-observe.ts +16 -6
- package/src/lifecycle/gate-ledger.test.ts +74 -2
- package/src/lifecycle/gate-ledger.ts +154 -36
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/release-ledger.ts +2 -2
- package/src/lifecycle/run-ledger.test.ts +268 -0
- package/src/lifecycle/run-ledger.ts +201 -0
- package/src/lifecycle/symptoms.ts +3 -3
- package/src/lint/config.test.ts +82 -1
- package/src/lint/config.ts +112 -3
- package/src/lint/pipeline-change-gate.test.ts +2 -2
- package/src/lint/policy.test.ts +1 -1
- package/src/lint/post-synth.test.ts +31 -0
- package/src/lint/post-synth.ts +54 -1
- package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
- package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
- package/src/lint/rules/comp/comp.test.ts +27 -1
- package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +76 -0
- package/src/lint/rules/comp/index.ts +3 -3
- package/src/lint/rules/op/index.ts +23 -0
- package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
- package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
- package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
- package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
- package/src/lint/rules/op/support.ts +11 -0
- package/src/lint/suppressions.test.ts +163 -0
- package/src/lint/suppressions.ts +204 -0
- package/src/live-endpoint.ts +1 -1
- package/src/op/activities/activity-contracts.ts +66 -0
- package/src/op/activities/apply.test.ts +567 -0
- package/src/op/activities/apply.ts +662 -0
- package/src/op/activities/build.ts +26 -0
- package/src/op/activities/converge.test.ts +330 -0
- package/src/op/activities/converge.ts +449 -0
- package/src/op/activities/env-teardown.test.ts +187 -0
- package/src/op/activities/env-teardown.ts +171 -0
- package/src/op/activities/guard-validate.test.ts +139 -0
- package/src/op/activities/guard-validate.ts +223 -0
- package/src/op/activities/http-check.test.ts +48 -0
- package/src/op/activities/http-check.ts +55 -0
- package/src/op/activities/index.ts +119 -0
- package/src/op/activities/lexicon-upgrade.test.ts +492 -0
- package/src/op/activities/lexicon-upgrade.ts +673 -0
- package/src/op/activities/lifecycle.ts +85 -0
- package/src/op/activities/pipeline-audit.test.ts +67 -0
- package/src/op/activities/pipeline-audit.ts +201 -0
- package/src/op/activities/policy.test.ts +19 -0
- package/src/op/activities/policy.ts +37 -0
- package/src/op/activities/reconcile.test.ts +161 -0
- package/src/op/activities/reconcile.ts +354 -0
- package/src/op/activities/shell.ts +26 -0
- package/src/op/activities/teardown.ts +22 -0
- package/src/op/activities/util.ts +6 -0
- package/src/op/activities/wait.ts +69 -0
- package/src/op/activities/workflow-audit.test.ts +90 -0
- package/src/op/activities/workflow-audit.ts +203 -0
- package/src/op/activity-contract-registry.test.ts +98 -0
- package/src/op/activity-contract-registry.ts +114 -0
- package/src/op/activity-contract.test.ts +1 -1
- package/src/op/activity-contract.ts +37 -33
- package/src/op/activity-failure.ts +34 -0
- package/src/op/activity-profiles.test.ts +56 -0
- package/src/op/activity-profiles.ts +116 -0
- package/src/op/activity-registry.test.ts +37 -28
- package/src/op/activity-registry.ts +27 -56
- package/src/op/activity-runtime.ts +3 -47
- package/src/op/builders-exports.test.ts +0 -49
- package/src/op/builders-typed.test.ts +164 -0
- package/src/op/builders.ts +125 -41
- package/src/op/change-signal.test.ts +117 -0
- package/src/op/change-signal.ts +169 -0
- package/src/op/composites/apply-op.ts +218 -0
- package/src/op/composites/composites.test.ts +379 -0
- package/src/op/composites/converge-op.test.ts +168 -0
- package/src/op/composites/converge-op.ts +214 -0
- package/src/op/composites/index.ts +26 -0
- package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
- package/src/op/composites/lexicon-upgrade-op.ts +134 -0
- package/src/op/composites/pipeline-audit-op.test.ts +25 -0
- package/src/op/composites/pipeline-audit-op.ts +78 -0
- package/src/op/composites/reconcile-op.test.ts +51 -0
- package/src/op/composites/reconcile-op.ts +110 -0
- package/src/op/composites/watch-op.ts +108 -0
- package/src/op/composites/workflow-audit-op.test.ts +25 -0
- package/src/op/composites/workflow-audit-op.ts +88 -0
- package/src/op/converge-rule.ts +12 -10
- package/src/op/cron.test.ts +128 -0
- package/src/op/cron.ts +149 -0
- package/src/op/discover-root.test.ts +105 -1
- package/src/op/discover.test.ts +6 -2
- package/src/op/discover.ts +84 -16
- package/src/op/duration.ts +21 -0
- package/src/op/effect-step.test.ts +29 -14
- package/src/op/emulator-lifecycle.ts +1 -2
- package/src/op/gate-name.test.ts +65 -0
- package/src/op/gate-name.ts +60 -0
- package/src/op/gate-summary.ts +84 -0
- package/src/op/gate.ts +155 -0
- package/src/op/generate-pipeline.test.ts +65 -3
- package/src/op/generate-pipeline.ts +19 -2
- package/src/op/index.ts +48 -8
- package/src/op/local-executor.test.ts +178 -34
- package/src/op/local-executor.ts +371 -102
- package/src/op/local-output.test.ts +95 -11
- package/src/op/local-output.ts +33 -4
- package/src/op/op-ir.test.ts +292 -0
- package/src/op/op-ir.ts +419 -0
- package/src/op/op-verb-class.test.ts +2 -2
- package/src/op/op-verb-class.ts +19 -5
- package/src/op/op.test.ts +28 -7
- package/src/op/operator.test.ts +543 -6
- package/src/op/operator.ts +203 -20
- package/src/op/receipt-store.ts +1 -1
- package/src/op/resource.ts +20 -2
- package/src/op/runtime.ts +203 -0
- package/src/op/runtimes/local.test.ts +181 -0
- package/src/op/runtimes/local.ts +212 -0
- package/src/op/step-output-ref.ts +15 -13
- package/src/op/types.ts +106 -26
- package/src/ownership.ts +1 -1
- package/src/provenance.test.ts +6 -0
- package/src/provenance.ts +21 -1
- package/src/serializer-walker.test.ts +42 -0
- package/src/serializer-walker.ts +21 -2
- package/src/terraform/aws-resources.test.ts +13 -4
- package/src/terraform/bridge.test.ts +22 -9
- package/src/terraform/bridge.ts +89 -28
- package/src/terraform/carve-provider.ts +38 -2
- package/src/terraform/data-source-shape.ts +95 -0
- package/src/terraform/graph.ts +35 -7
- package/src/terraform/providers/kubernetes.ts +48 -2
- package/src/terraform/tier-map.ts +21 -6
- package/src/terraform/types.ts +8 -0
- package/dist/cli/handlers/op-progress.d.ts +0 -90
- package/dist/cli/handlers/op-progress.d.ts.map +0 -1
- package/dist/cli/handlers/run-client.d.ts +0 -127
- package/dist/cli/handlers/run-client.d.ts.map +0 -1
- package/dist/cli/handlers/run-report.d.ts +0 -5
- package/dist/cli/handlers/run-report.d.ts.map +0 -1
- package/dist/components/temporal-codegen-loader.d.ts +0 -27
- package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
- package/src/cli/handlers/op-progress.test.ts +0 -268
- package/src/cli/handlers/op-progress.ts +0 -248
- package/src/cli/handlers/run-client.test.ts +0 -82
- package/src/cli/handlers/run-client.ts +0 -219
- package/src/cli/handlers/run-report.test.ts +0 -62
- package/src/cli/handlers/run-report.ts +0 -122
- package/src/components/temporal-codegen-loader.ts +0 -41
- package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fold provenance (#2161) — which composite parameter produced which emitted field.
|
|
3
|
+
*
|
|
4
|
+
* A composite is the one lossy thing chant emits. It takes a few typed
|
|
5
|
+
* arguments and expands to several resources, so a return leg that regenerates
|
|
6
|
+
* source from live produces flat typed resources and the composite is gone. The
|
|
7
|
+
* projection is not invertible from its output alone, which is the view-update
|
|
8
|
+
* problem landing on chant's record.
|
|
9
|
+
*
|
|
10
|
+
* chant can answer it where a general generator cannot, and for one reason: the
|
|
11
|
+
* evaluated subset is restricted on purpose. `discovery/fold-import.ts` already
|
|
12
|
+
* interprets an admissible factory body rather than calling it, so the
|
|
13
|
+
* expression each member property was WRITTEN as is in hand at build time. The
|
|
14
|
+
* same pass records where the field came from, in `discovery/param-deps.ts`'s
|
|
15
|
+
* {@link import("./discovery/param-deps").collectCompositeOrigins}.
|
|
16
|
+
*
|
|
17
|
+
* ## Four answers, and the fourth is a real one
|
|
18
|
+
*
|
|
19
|
+
* Every emitted path gets one of {@link FoldFieldOrigin}'s four kinds. Three of
|
|
20
|
+
* them are findings; `unknown` is the fourth, and it is a legal answer rather
|
|
21
|
+
* than a gap. A build that expanded a composite without interpreting its body
|
|
22
|
+
* (the run path, a factory outside the admissible subset, a sandboxed child
|
|
23
|
+
* whose provenance did not survive the wire) knows the field came from a
|
|
24
|
+
* composite and cannot say which parameter. Recording that is the whole point:
|
|
25
|
+
* an unknown must never fall through to `direct`, because `direct` licenses
|
|
26
|
+
* editing the field in the user's source and an unattributed composite field is
|
|
27
|
+
* exactly the case where that produces a flattened resource.
|
|
28
|
+
*
|
|
29
|
+
* `direct` is claimed only where the build has a provenance record AND that
|
|
30
|
+
* record says no composite expanded the entity. Absence of a record is
|
|
31
|
+
* `unknown`, never `direct`.
|
|
32
|
+
*
|
|
33
|
+
* ## Beside the output, never in it
|
|
34
|
+
*
|
|
35
|
+
* The record is build metadata, not declared configuration, and it must not
|
|
36
|
+
* reach the document an applier writes. It rides two channels, both outside the
|
|
37
|
+
* apply-bound document, and neither is a new mechanism:
|
|
38
|
+
*
|
|
39
|
+
* - per entity, the non-enumerable symbol-keyed side channel `./provenance.ts`
|
|
40
|
+
* already owns, invisible to every serializer and to every spread;
|
|
41
|
+
* - per build, {@link import("./build").BuildResult.foldProvenance}, a sibling
|
|
42
|
+
* of `buildParams` and `lexiconVersions`.
|
|
43
|
+
*
|
|
44
|
+
* Nothing is added to `SerializeContext`. That is the same posture
|
|
45
|
+
* `./effect-receipt.ts` takes at the same seam, one notch stricter: a receipt
|
|
46
|
+
* rides `SerializeContext.receipts` so a lexicon MAY render it outside
|
|
47
|
+
* `Resources`, whereas provenance has no rendering and is simply never handed
|
|
48
|
+
* to a serializer at all. `fold-provenance-exclusion.test.ts` drives a real
|
|
49
|
+
* build through a spy serializer and a mock applier to hold that line.
|
|
50
|
+
*
|
|
51
|
+
* ## The return leg
|
|
52
|
+
*
|
|
53
|
+
* {@link resolveDriftedField} is the consumer. #2160 has already ruled out the
|
|
54
|
+
* fields nobody declared, so a drifted field reaching it is one source actually
|
|
55
|
+
* declared, and the origin decides what may be proposed:
|
|
56
|
+
*
|
|
57
|
+
* - a composite parameter: propose changing that parameter, naming the file,
|
|
58
|
+
* the composite call and the parameter path. One line, composite intact.
|
|
59
|
+
* - a literal the composite fixes: refuse by name. The choice is to
|
|
60
|
+
* parameterize the field or stop using the composite there, and neither is
|
|
61
|
+
* something chant may pick.
|
|
62
|
+
* - a direct declaration: today's behaviour, unchanged.
|
|
63
|
+
* - unknown: today's behaviour, and say that is what happened.
|
|
64
|
+
*
|
|
65
|
+
* Proposing is as far as this goes. Rewriting a parameter in the user's source
|
|
66
|
+
* is a source-edit problem (the generator half of `chant import` emits whole
|
|
67
|
+
* files and nothing maps a property path back to a location in a `.ts` file),
|
|
68
|
+
* so a proposal here is a described action: it names the file, the composite
|
|
69
|
+
* call and the parameter path, and performs no edit.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
import { isResourceDeclarable, type Declarable } from "./declarable";
|
|
73
|
+
import { originOfPath, type EntityProvenance, type PathOrigin } from "./provenance";
|
|
74
|
+
import type { DeepEntityDrift, PropertyDrift } from "./lifecycle/deep-diff";
|
|
75
|
+
|
|
76
|
+
/** Why a field's origin could not be determined. Both are facts about the BUILD, not about the field. */
|
|
77
|
+
export type UnknownOriginReason =
|
|
78
|
+
/**
|
|
79
|
+
* A composite expanded the entity and this build did not interpret its body,
|
|
80
|
+
* so parameter and fixed literal are indistinguishable. The run path, a
|
|
81
|
+
* factory outside `fold-import.ts`'s admissible subset, and a `propagate()`
|
|
82
|
+
* key both sides wrote all land here.
|
|
83
|
+
*/
|
|
84
|
+
| "composite-not-interpreted"
|
|
85
|
+
/**
|
|
86
|
+
* The build recorded no provenance for this entity at all, so not even "did a
|
|
87
|
+
* composite make it" is answerable. A sandboxed child's entities arrive this
|
|
88
|
+
* way: `discovery/entity-wire-codec.ts` drops build metadata over the wire.
|
|
89
|
+
*/
|
|
90
|
+
| "no-provenance";
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* What produced one emitted property path.
|
|
94
|
+
*
|
|
95
|
+
* The composite kinds carry `instance` — the export name every member of one
|
|
96
|
+
* composite call shares — because "the `AutoscaledService` composite" is not an
|
|
97
|
+
* address and "the `api` call of `AutoscaledService`" is.
|
|
98
|
+
*/
|
|
99
|
+
export type FoldFieldOrigin =
|
|
100
|
+
| { kind: "composite-parameter"; composite: string; instance?: string; parameters: string[] }
|
|
101
|
+
| { kind: "composite-literal"; composite: string; instance?: string }
|
|
102
|
+
| { kind: "direct" }
|
|
103
|
+
| { kind: "unknown"; reason: UnknownOriginReason };
|
|
104
|
+
|
|
105
|
+
/** One entity's fold provenance: where it was declared, and an origin for every field it emits. */
|
|
106
|
+
export interface EntityFoldProvenance {
|
|
107
|
+
/** The source file that declared the entity, when the build recorded one. */
|
|
108
|
+
sourceFile?: string;
|
|
109
|
+
/** The composite type that expanded it, when one did. */
|
|
110
|
+
composite?: string;
|
|
111
|
+
/** The composite call (export name) it belongs to, when one expanded it. */
|
|
112
|
+
instance?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Emitted property path to origin, in sorted key order. Every path the entity
|
|
115
|
+
* emits is present: a path with no attribution carries an `unknown` origin
|
|
116
|
+
* rather than being left out, so a consumer iterating fields never has to
|
|
117
|
+
* decide what a missing key meant.
|
|
118
|
+
*/
|
|
119
|
+
fields: Record<string, FoldFieldOrigin>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** A whole build's fold provenance, keyed by chant entity name. */
|
|
123
|
+
export type FoldProvenance = Record<string, EntityFoldProvenance>;
|
|
124
|
+
|
|
125
|
+
/** True for an ordinary `{}`, false for an array, a class instance, an `AttrRef`. */
|
|
126
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
127
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
128
|
+
const proto = Object.getPrototypeOf(value) as unknown;
|
|
129
|
+
return proto === Object.prototype || proto === null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Every property path an entity's `props` emits, in `EntityProvenance.paths`'
|
|
134
|
+
* grammar: dotted names only, plain objects descended into, everything else
|
|
135
|
+
* attributed whole at its own path.
|
|
136
|
+
*
|
|
137
|
+
* Arrays are deliberately not indexed. An index-shaped key would not survive an
|
|
138
|
+
* element moving, and the recorder that produces origins cannot index either,
|
|
139
|
+
* so indexing here would manufacture paths nothing can ever attribute. The diff
|
|
140
|
+
* addresses array elements with `[#key]`/`[n]` and `originOfPath` resolves
|
|
141
|
+
* those against the dotted key that covers them, which is the join between the
|
|
142
|
+
* two grammars.
|
|
143
|
+
*
|
|
144
|
+
* An empty object is a value, and a leaf. An `undefined` value is not emitted
|
|
145
|
+
* and yields no path.
|
|
146
|
+
*/
|
|
147
|
+
export function emittedFieldPaths(props: unknown): string[] {
|
|
148
|
+
const out: string[] = [];
|
|
149
|
+
|
|
150
|
+
const walk = (value: unknown, prefix: string): void => {
|
|
151
|
+
if (value === undefined) return;
|
|
152
|
+
if (isPlainObject(value)) {
|
|
153
|
+
const keys = Object.keys(value).sort();
|
|
154
|
+
if (keys.length === 0) {
|
|
155
|
+
if (prefix) out.push(prefix);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
for (const key of keys) walk(value[key], prefix ? `${prefix}.${key}` : key);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (prefix) out.push(prefix);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
walk(props, "");
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The four-way answer for one path, from the recorded {@link PathOrigin} and
|
|
170
|
+
* the entity's own provenance.
|
|
171
|
+
*
|
|
172
|
+
* `origin` is what {@link originOfPath} resolved for the path, so a coarse key
|
|
173
|
+
* recorded at an ancestor governs a leaf, exactly as it does everywhere else.
|
|
174
|
+
*/
|
|
175
|
+
export function classifyFieldOrigin(
|
|
176
|
+
origin: PathOrigin | undefined,
|
|
177
|
+
provenance: EntityProvenance | undefined,
|
|
178
|
+
): FoldFieldOrigin {
|
|
179
|
+
if (!provenance) return { kind: "unknown", reason: "no-provenance" };
|
|
180
|
+
const instance = provenance.compositeInstance;
|
|
181
|
+
|
|
182
|
+
switch (origin?.kind) {
|
|
183
|
+
case "composite-parameter":
|
|
184
|
+
return {
|
|
185
|
+
kind: "composite-parameter",
|
|
186
|
+
composite: origin.composite,
|
|
187
|
+
...(instance ? { instance } : {}),
|
|
188
|
+
parameters: [...origin.parameters],
|
|
189
|
+
};
|
|
190
|
+
case "composite-literal":
|
|
191
|
+
return { kind: "composite-literal", composite: origin.composite, ...(instance ? { instance } : {}) };
|
|
192
|
+
case "composite":
|
|
193
|
+
// The coarse kind: a composite expanded this, and nothing said which
|
|
194
|
+
// parameter. Not a parameter, not a fixed literal, and above all not a
|
|
195
|
+
// direct declaration.
|
|
196
|
+
return { kind: "unknown", reason: "composite-not-interpreted" };
|
|
197
|
+
case "authored":
|
|
198
|
+
case "build-param":
|
|
199
|
+
// A build parameter governs the expression the AUTHOR wrote in their own
|
|
200
|
+
// source, so the declaration is still theirs to edit.
|
|
201
|
+
return { kind: "direct" };
|
|
202
|
+
case undefined:
|
|
203
|
+
return provenance.composite
|
|
204
|
+
? { kind: "unknown", reason: "composite-not-interpreted" }
|
|
205
|
+
: { kind: "direct" };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** One entity's fold provenance, or `undefined` when it emits no properties at all. */
|
|
210
|
+
export function foldProvenanceOfEntity(
|
|
211
|
+
entity: Declarable,
|
|
212
|
+
provenance: EntityProvenance | undefined,
|
|
213
|
+
): EntityFoldProvenance | undefined {
|
|
214
|
+
if (!isResourceDeclarable(entity)) return undefined;
|
|
215
|
+
const paths = emittedFieldPaths(entity.props);
|
|
216
|
+
if (paths.length === 0) return undefined;
|
|
217
|
+
|
|
218
|
+
const fields: Record<string, FoldFieldOrigin> = {};
|
|
219
|
+
for (const path of paths) {
|
|
220
|
+
fields[path] = classifyFieldOrigin(originOfPath(provenance?.paths, path), provenance);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
...(provenance?.sourceFile ? { sourceFile: provenance.sourceFile } : {}),
|
|
225
|
+
...(provenance?.composite ? { composite: provenance.composite } : {}),
|
|
226
|
+
...(provenance?.compositeInstance ? { instance: provenance.compositeInstance } : {}),
|
|
227
|
+
fields,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A whole build's fold provenance.
|
|
233
|
+
*
|
|
234
|
+
* `provenanceOf` is injected rather than read off the entity here so the same
|
|
235
|
+
* function serves a build (where it is `getProvenance`) and a test with a
|
|
236
|
+
* hand-written record. Entities with no properties are absent; sorted, because
|
|
237
|
+
* a build's outputs are compared byte for byte.
|
|
238
|
+
*/
|
|
239
|
+
export function foldProvenanceOfEntities(
|
|
240
|
+
entities: ReadonlyMap<string, Declarable>,
|
|
241
|
+
provenanceOf: (entity: Declarable) => EntityProvenance | undefined,
|
|
242
|
+
): FoldProvenance {
|
|
243
|
+
const out: FoldProvenance = {};
|
|
244
|
+
for (const name of [...entities.keys()].sort()) {
|
|
245
|
+
const entity = entities.get(name) as Declarable;
|
|
246
|
+
const record = foldProvenanceOfEntity(entity, provenanceOf(entity));
|
|
247
|
+
if (record) out[name] = record;
|
|
248
|
+
}
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** One-line rendering of a field origin, for a report. */
|
|
253
|
+
export function describeFoldFieldOrigin(origin: FoldFieldOrigin): string {
|
|
254
|
+
switch (origin.kind) {
|
|
255
|
+
case "composite-parameter":
|
|
256
|
+
return `parameter ${origin.parameters.join(", ")} of ${compositeCall(origin.composite, origin.instance)}`;
|
|
257
|
+
case "composite-literal":
|
|
258
|
+
return `fixed by ${compositeCall(origin.composite, origin.instance)}`;
|
|
259
|
+
case "direct":
|
|
260
|
+
return "declared directly";
|
|
261
|
+
case "unknown":
|
|
262
|
+
return `origin unknown (${unknownOriginText(origin.reason)})`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Why an origin could not be determined, in words a report can print. */
|
|
267
|
+
export function unknownOriginText(reason: UnknownOriginReason): string {
|
|
268
|
+
switch (reason) {
|
|
269
|
+
case "composite-not-interpreted":
|
|
270
|
+
return "expanded by a composite whose factory this build did not interpret";
|
|
271
|
+
case "no-provenance":
|
|
272
|
+
return "this build recorded no provenance for the entity";
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** `AutoscaledService composite call \`api\``, or just the composite when the call has no name. */
|
|
277
|
+
function compositeCall(composite: string, instance: string | undefined): string {
|
|
278
|
+
return instance ? `the \`${instance}\` call of composite ${composite}` : `composite ${composite}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** `in src/app.infra.ts`, or nothing when the build recorded no file. */
|
|
282
|
+
function inFile(sourceFile: string | undefined): string {
|
|
283
|
+
return sourceFile ? ` in ${sourceFile}` : "";
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** A value as a report prints it. */
|
|
287
|
+
function renderValue(value: unknown): string {
|
|
288
|
+
if (value === undefined) return "unset";
|
|
289
|
+
try {
|
|
290
|
+
return JSON.stringify(value) ?? String(value);
|
|
291
|
+
} catch {
|
|
292
|
+
return String(value);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* What the return leg may do about one drifted field.
|
|
298
|
+
*
|
|
299
|
+
* Every arm is a DESCRIBED action. Nothing here edits a file, and
|
|
300
|
+
* `propose-parameter` deliberately stops at naming the edit: the source-edit
|
|
301
|
+
* half is the generator side of `chant import` and does not exist yet.
|
|
302
|
+
*/
|
|
303
|
+
export type DriftResolution =
|
|
304
|
+
| {
|
|
305
|
+
kind: "propose-parameter";
|
|
306
|
+
composite: string;
|
|
307
|
+
instance?: string;
|
|
308
|
+
sourceFile?: string;
|
|
309
|
+
/** The factory parameter paths that govern the field. */
|
|
310
|
+
parameters: string[];
|
|
311
|
+
description: string;
|
|
312
|
+
}
|
|
313
|
+
| {
|
|
314
|
+
kind: "refuse-fixed";
|
|
315
|
+
composite: string;
|
|
316
|
+
instance?: string;
|
|
317
|
+
sourceFile?: string;
|
|
318
|
+
description: string;
|
|
319
|
+
}
|
|
320
|
+
| { kind: "edit-declaration"; sourceFile?: string; description: string }
|
|
321
|
+
| { kind: "fall-back"; reason: UnknownOriginReason; sourceFile?: string; description: string };
|
|
322
|
+
|
|
323
|
+
/** One drifted field and what may be done about it. */
|
|
324
|
+
export interface FieldReconcile {
|
|
325
|
+
entity: string;
|
|
326
|
+
path: string;
|
|
327
|
+
declared?: unknown;
|
|
328
|
+
live?: unknown;
|
|
329
|
+
origin: FoldFieldOrigin;
|
|
330
|
+
resolution: DriftResolution;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Classify one drifted field by its origin.
|
|
335
|
+
*
|
|
336
|
+
* The caller has already established this is drift in #2160's sense: a path
|
|
337
|
+
* source declared, whose live value moved. Undeclared fields are held
|
|
338
|
+
* elsewhere and never reach here, so the only question left is which of the
|
|
339
|
+
* four origins produced the field.
|
|
340
|
+
*/
|
|
341
|
+
export function resolveDriftedField(input: {
|
|
342
|
+
entity: string;
|
|
343
|
+
path: string;
|
|
344
|
+
declared?: unknown;
|
|
345
|
+
live?: unknown;
|
|
346
|
+
origin?: PathOrigin;
|
|
347
|
+
provenance?: EntityProvenance;
|
|
348
|
+
}): FieldReconcile {
|
|
349
|
+
const origin = classifyFieldOrigin(input.origin, input.provenance);
|
|
350
|
+
const sourceFile = input.provenance?.sourceFile;
|
|
351
|
+
const move = `${renderValue(input.declared)} to ${renderValue(input.live)}`;
|
|
352
|
+
|
|
353
|
+
const resolution: DriftResolution = ((): DriftResolution => {
|
|
354
|
+
switch (origin.kind) {
|
|
355
|
+
case "composite-parameter": {
|
|
356
|
+
const parameters = origin.parameters;
|
|
357
|
+
return {
|
|
358
|
+
kind: "propose-parameter",
|
|
359
|
+
composite: origin.composite,
|
|
360
|
+
...(origin.instance ? { instance: origin.instance } : {}),
|
|
361
|
+
...(sourceFile ? { sourceFile } : {}),
|
|
362
|
+
parameters: [...parameters],
|
|
363
|
+
description:
|
|
364
|
+
`change ${parameters.length === 1 ? "parameter" : "parameters"} ` +
|
|
365
|
+
`${parameters.map((p) => `\`${p}\``).join(", ")} of ` +
|
|
366
|
+
`${compositeCall(origin.composite, origin.instance)}${inFile(sourceFile)} ` +
|
|
367
|
+
`so \`${input.path}\` moves from ${move}. The composite stays.`,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
case "composite-literal":
|
|
371
|
+
return {
|
|
372
|
+
kind: "refuse-fixed",
|
|
373
|
+
composite: origin.composite,
|
|
374
|
+
...(origin.instance ? { instance: origin.instance } : {}),
|
|
375
|
+
...(sourceFile ? { sourceFile } : {}),
|
|
376
|
+
description:
|
|
377
|
+
`refused: \`${input.path}\` is fixed by ${compositeCall(origin.composite, origin.instance)}` +
|
|
378
|
+
`${inFile(sourceFile)}, so no argument at the call site moves it. ` +
|
|
379
|
+
`Parameterize the field in composite ${origin.composite}, or stop using the composite here.`,
|
|
380
|
+
};
|
|
381
|
+
case "direct":
|
|
382
|
+
return {
|
|
383
|
+
kind: "edit-declaration",
|
|
384
|
+
...(sourceFile ? { sourceFile } : {}),
|
|
385
|
+
description: `change the declared value of \`${input.path}\`${inFile(sourceFile)} from ${move}.`,
|
|
386
|
+
};
|
|
387
|
+
case "unknown":
|
|
388
|
+
return {
|
|
389
|
+
kind: "fall-back",
|
|
390
|
+
reason: origin.reason,
|
|
391
|
+
...(sourceFile ? { sourceFile } : {}),
|
|
392
|
+
description:
|
|
393
|
+
`origin unknown (${unknownOriginText(origin.reason)}), so falling back to changing the ` +
|
|
394
|
+
`declared value of \`${input.path}\`${inFile(sourceFile)} from ${move}.`,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
})();
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
entity: input.entity,
|
|
401
|
+
path: input.path,
|
|
402
|
+
...("declared" in input ? { declared: input.declared } : {}),
|
|
403
|
+
...("live" in input ? { live: input.live } : {}),
|
|
404
|
+
origin,
|
|
405
|
+
resolution,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Every drifted field in a deep diff, classified.
|
|
411
|
+
*
|
|
412
|
+
* Reads `drifted` only. `unclaimed` is somebody else's field and
|
|
413
|
+
* `accepted` is a difference the baseline holds back, and neither may become an
|
|
414
|
+
* update, so neither is offered a resolution.
|
|
415
|
+
*
|
|
416
|
+
* A drift row of kind `absent` has no live value: the field is declared and the
|
|
417
|
+
* cloud does not have it. That is still drift on a declared path, and the same
|
|
418
|
+
* four origins decide what may be proposed about it.
|
|
419
|
+
*/
|
|
420
|
+
export function resolveDeepDrift(
|
|
421
|
+
drifted: readonly DeepEntityDrift[],
|
|
422
|
+
provenanceOf: (entity: string) => EntityProvenance | undefined,
|
|
423
|
+
): FieldReconcile[] {
|
|
424
|
+
const out: FieldReconcile[] = [];
|
|
425
|
+
for (const entity of drifted) {
|
|
426
|
+
const provenance = provenanceOf(entity.name);
|
|
427
|
+
for (const change of entity.changes as readonly PropertyDrift[]) {
|
|
428
|
+
out.push(
|
|
429
|
+
resolveDriftedField({
|
|
430
|
+
entity: entity.name,
|
|
431
|
+
path: change.path,
|
|
432
|
+
...("declared" in change ? { declared: change.declared } : {}),
|
|
433
|
+
...("live" in change ? { live: change.live } : {}),
|
|
434
|
+
...(change.origin ? { origin: change.origin } : {}),
|
|
435
|
+
...(provenance ? { provenance } : {}),
|
|
436
|
+
}),
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
441
|
+
}
|
package/src/graph-ops.test.ts
CHANGED
|
@@ -50,14 +50,14 @@ describe("mergeProjectOps (#1675)", () => {
|
|
|
50
50
|
|
|
51
51
|
const ir = buildGraphIr(result.entities, root);
|
|
52
52
|
const node = ir.nodes.find((n) => n.id === "deploy");
|
|
53
|
-
expect(node).toMatchObject({ kind: "
|
|
53
|
+
expect(node).toMatchObject({ kind: "Chant::Op", lexicon: "chant", sourceLoc: { file: "deploy.op.ts" } });
|
|
54
54
|
expect(node?.attrs.name).toBe("deploy");
|
|
55
55
|
expect(node?.attrs.depends).toEqual(["inner"]);
|
|
56
56
|
expect(node?.attrs.phases).toEqual([
|
|
57
|
-
{ name: "Apply", steps: [{ kind: "activity", fn: "build" }, { kind: "gate",
|
|
57
|
+
{ name: "Apply", steps: [{ kind: "activity", fn: "build" }, { kind: "gate", gate: "approve" }] },
|
|
58
58
|
]);
|
|
59
59
|
// The sourceDir op discovery already loaded is untouched, not duplicated.
|
|
60
|
-
expect(ir.nodes.filter((n) => n.kind === "
|
|
60
|
+
expect(ir.nodes.filter((n) => n.kind === "Chant::Op")).toHaveLength(2);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
test("is idempotent — a second merge adds nothing", async () => {
|
package/src/graph-ops.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { setProvenance } from "./provenance";
|
|
|
10
10
|
* root beside `sourceDir: "src"`), and `chant op` / `chant graph` find them via
|
|
11
11
|
* {@link discoverOps}, which scans from the git root. Join the two scopes: every
|
|
12
12
|
* Op `discoverOps` finds that discovery did not already load becomes a
|
|
13
|
-
* `
|
|
13
|
+
* `Chant::Op` entity in the map, keyed the way discovery keys a default
|
|
14
14
|
* export (the file's basename sans `.op.ts`), so the IR carries the declared
|
|
15
15
|
* DAG whatever the layout. Ops discovery already loaded (same file) are left
|
|
16
16
|
* alone. Returns the op-discovery errors for the caller to surface.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `heldElsewhere()` (#2162) — the runtime marker itself, its JSON-safe
|
|
3
|
+
* normalized form, and the type-checking contract the compile-time-only
|
|
4
|
+
* section proves against a real generated-style property interface.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, test, expect } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
HELD_ELSEWHERE_MARKER,
|
|
10
|
+
HELD_ELSEWHERE_TAG,
|
|
11
|
+
heldElsewhere,
|
|
12
|
+
isHeldElsewhere,
|
|
13
|
+
isNormalizedHeldElsewhere,
|
|
14
|
+
normalizeHeldElsewhere,
|
|
15
|
+
} from "./held-elsewhere";
|
|
16
|
+
|
|
17
|
+
describe("heldElsewhere()", () => {
|
|
18
|
+
test("returns a marker carrying by/reason, symbol-tagged", () => {
|
|
19
|
+
const marker = heldElsewhere<number>({ by: "hpa", reason: "the autoscaler owns replicas after the first apply" });
|
|
20
|
+
expect(isHeldElsewhere(marker)).toBe(true);
|
|
21
|
+
expect((marker as unknown as { by: string }).by).toBe("hpa");
|
|
22
|
+
expect((marker as unknown as { reason: string }).reason).toBe("the autoscaler owns replicas after the first apply");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("isHeldElsewhere is false for an ordinary value and for a lookalike object missing the symbol", () => {
|
|
26
|
+
expect(isHeldElsewhere(3)).toBe(false);
|
|
27
|
+
expect(isHeldElsewhere(undefined)).toBe(false);
|
|
28
|
+
expect(isHeldElsewhere(null)).toBe(false);
|
|
29
|
+
// Same shape, no marker: a plain object an author happened to write with
|
|
30
|
+
// `by`/`reason` keys must never be mistaken for the real thing.
|
|
31
|
+
expect(isHeldElsewhere({ by: "hpa", reason: "not actually held" })).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("the marker's own symbol key never enumerates through Object.entries/JSON.stringify", () => {
|
|
35
|
+
const marker = heldElsewhere<number>({ by: "hpa", reason: "x" });
|
|
36
|
+
expect(Object.entries(marker as unknown as object)).toEqual([
|
|
37
|
+
["by", "hpa"],
|
|
38
|
+
["reason", "x"],
|
|
39
|
+
]);
|
|
40
|
+
// The marker key is invisible to a JSON round-trip — this is exactly why
|
|
41
|
+
// `deep-observation.ts` normalizes it to a JSON-safe tagged form instead
|
|
42
|
+
// of relying on the symbol surviving a `--json` snapshot.
|
|
43
|
+
expect(JSON.parse(JSON.stringify(marker))).toEqual({ by: "hpa", reason: "x" });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("HELD_ELSEWHERE_MARKER is the well-known chant symbol, stable across module instances", () => {
|
|
47
|
+
expect(HELD_ELSEWHERE_MARKER).toBe(Symbol.for("chant.heldElsewhere"));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("normalizeHeldElsewhere() / isNormalizedHeldElsewhere()", () => {
|
|
52
|
+
test("normalizes to a JSON-safe tagged object", () => {
|
|
53
|
+
const marker = heldElsewhere<number>({ by: "hpa", reason: "x" });
|
|
54
|
+
const normalized = normalizeHeldElsewhere(marker as never);
|
|
55
|
+
expect(normalized).toEqual({ heldElsewhere: HELD_ELSEWHERE_TAG, by: "hpa", reason: "x" });
|
|
56
|
+
expect(isNormalizedHeldElsewhere(normalized)).toBe(true);
|
|
57
|
+
// Round-trips through JSON — this is the shape that actually rides in
|
|
58
|
+
// `--json` output, unlike the symbol-tagged runtime marker above.
|
|
59
|
+
expect(isNormalizedHeldElsewhere(JSON.parse(JSON.stringify(normalized)))).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("isNormalizedHeldElsewhere rejects an ordinary object and the un-normalized runtime marker", () => {
|
|
63
|
+
expect(isNormalizedHeldElsewhere({ by: "hpa", reason: "x" })).toBe(false);
|
|
64
|
+
expect(isNormalizedHeldElsewhere(heldElsewhere<number>({ by: "hpa", reason: "x" }))).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ── Compile-time-only: authoring-time type errors (never executed) ──────────
|
|
69
|
+
//
|
|
70
|
+
// A generated resource's props interface, shaped like a real lexicon's
|
|
71
|
+
// generated constructor argument (chant #2162's whole point: `heldElsewhere`
|
|
72
|
+
// must typecheck in exactly the position the property's own value would go,
|
|
73
|
+
// and nowhere else).
|
|
74
|
+
interface DeploymentSpecProps {
|
|
75
|
+
replicas?: number;
|
|
76
|
+
image: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _typeChecksOnly(): void {
|
|
80
|
+
// The success case: assignable where the property's own value goes,
|
|
81
|
+
// exactly like a literal `replicas: 3` would be.
|
|
82
|
+
const ok: DeploymentSpecProps = {
|
|
83
|
+
image: "my-app:latest",
|
|
84
|
+
replicas: heldElsewhere({ by: "hpa", reason: "the autoscaler owns replicas after the first apply" }),
|
|
85
|
+
};
|
|
86
|
+
void ok;
|
|
87
|
+
|
|
88
|
+
const badType: DeploymentSpecProps = {
|
|
89
|
+
image: "my-app:latest",
|
|
90
|
+
// @ts-expect-error — replicas is a number; heldElsewhere<string>() does not typecheck against it.
|
|
91
|
+
replicas: heldElsewhere<string>({ by: "hpa", reason: "x" }),
|
|
92
|
+
};
|
|
93
|
+
void badType;
|
|
94
|
+
|
|
95
|
+
const badProperty: DeploymentSpecProps = {
|
|
96
|
+
image: "my-app:latest",
|
|
97
|
+
// @ts-expect-error — "nope" is not a key of DeploymentSpecProps at all.
|
|
98
|
+
nope: heldElsewhere({ by: "hpa", reason: "x" }),
|
|
99
|
+
};
|
|
100
|
+
void badProperty;
|
|
101
|
+
}
|
|
102
|
+
void _typeChecksOnly;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `heldElsewhere` — a typed, per-property declaration that a field belongs to
|
|
3
|
+
* something else once the first apply happens (chant #2162).
|
|
4
|
+
*
|
|
5
|
+
* An autoscaler owns a Deployment's `replicas` after the first apply. A
|
|
6
|
+
* controller writes an annotation on every sync. An operator was granted one
|
|
7
|
+
* knob deliberately. None of these are drift, and reconciling them back to
|
|
8
|
+
* whatever chant last declared would fight the thing that is actually
|
|
9
|
+
* supposed to own the field.
|
|
10
|
+
*
|
|
11
|
+
* Terraform's answer is `lifecycle { ignore_changes = [...] }`: a list of
|
|
12
|
+
* strings, carrying no reason, invisible once written. This is the typed
|
|
13
|
+
* alternative — a marker written where the property's value would go, so a
|
|
14
|
+
* lexicon's generated types check it the same way they check any other value,
|
|
15
|
+
* and it cannot be attached to a property that does not exist.
|
|
16
|
+
*
|
|
17
|
+
* This is not a suppression comment. `chant-ignore` (#2111) hides a *lint*
|
|
18
|
+
* finding about source; a `heldElsewhere` marker is a fact about who operates
|
|
19
|
+
* a field at *runtime*, so it lives in the typed declaration, where
|
|
20
|
+
* `chant lifecycle plan` and `chant lifecycle diff --live` can render it,
|
|
21
|
+
* rather than in a comment only a linter reads.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Runtime-only marker, symbol-keyed so it never collides with a real
|
|
26
|
+
* property actually named `by` or `reason` and never serializes by accident
|
|
27
|
+
* through `JSON.stringify` or `Object.entries` (which do not visit symbol
|
|
28
|
+
* keys) — the two places a stray plain-object marker would otherwise leak
|
|
29
|
+
* into a snapshot or a wire payload unnoticed.
|
|
30
|
+
*/
|
|
31
|
+
export const HELD_ELSEWHERE_MARKER = Symbol.for("chant.heldElsewhere");
|
|
32
|
+
|
|
33
|
+
export interface HeldElsewhereOptions {
|
|
34
|
+
/** Who holds the field at runtime — the autoscaler, the controller, the operator. Free text: whatever names the holder on this substrate ("hpa", "cluster-autoscaler", "the on-call operator who set this by hand"). */
|
|
35
|
+
by: string;
|
|
36
|
+
/** Why chant does not reconcile this field after the first apply. */
|
|
37
|
+
reason: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The runtime shape a `heldElsewhere()` call actually produces. */
|
|
41
|
+
export interface HeldElsewhereMarker extends HeldElsewhereOptions {
|
|
42
|
+
readonly [HELD_ELSEWHERE_MARKER]: true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Declare a property held by something else after the first apply.
|
|
47
|
+
*
|
|
48
|
+
* Typed `T` — the property's own declared type — rather than
|
|
49
|
+
* `HeldElsewhereMarker`, so it type-checks in exactly the position the
|
|
50
|
+
* property's real value would go: `replicas: heldElsewhere({ by: "hpa",
|
|
51
|
+
* reason: "the autoscaler owns replicas after the first apply" })` against a
|
|
52
|
+
* generated `replicas?: number` typechecks the same way `replicas: 3` does,
|
|
53
|
+
* and writing it against a property that does not exist on the resource's
|
|
54
|
+
* props is an excess-property error like any other.
|
|
55
|
+
*
|
|
56
|
+
* The runtime value is the marker object, not a `T` — this function tells
|
|
57
|
+
* TypeScript otherwise on purpose. Every consumer that must tell the two
|
|
58
|
+
* apart checks {@link isHeldElsewhere} before trusting a declared value's
|
|
59
|
+
* real shape: the synth-time serializer (`serializer-walker.ts`, which omits
|
|
60
|
+
* the field from the applied payload so the property is never sent — the
|
|
61
|
+
* provider defaults it once at creation, and the holder owns it from there)
|
|
62
|
+
* and the deep-diff declared side (`deep-observation.ts`/`deep-diff.ts`,
|
|
63
|
+
* which reports a live difference as held rather than as drift).
|
|
64
|
+
*/
|
|
65
|
+
export function heldElsewhere<T>(opts: HeldElsewhereOptions): T {
|
|
66
|
+
const marker: HeldElsewhereMarker = {
|
|
67
|
+
[HELD_ELSEWHERE_MARKER]: true,
|
|
68
|
+
by: opts.by,
|
|
69
|
+
reason: opts.reason,
|
|
70
|
+
};
|
|
71
|
+
return marker as unknown as T;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* True when `value` is a {@link HeldElsewhereMarker} — the runtime shape a
|
|
76
|
+
* `heldElsewhere()` call produces, wherever it ends up (a resource's own
|
|
77
|
+
* prop, nested inside a property-kind Declarable's props).
|
|
78
|
+
*/
|
|
79
|
+
export function isHeldElsewhere(value: unknown): value is HeldElsewhereMarker {
|
|
80
|
+
return (
|
|
81
|
+
typeof value === "object" &&
|
|
82
|
+
value !== null &&
|
|
83
|
+
(value as Record<symbol, unknown>)[HELD_ELSEWHERE_MARKER] === true
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* JSON-safe tag a held marker normalizes to (see `deep-observation.ts`) so
|
|
89
|
+
* its identity survives normalization, `flattenDeepProperties`, and a
|
|
90
|
+
* `--json` round-trip. The symbol marker above does not serialize and
|
|
91
|
+
* `Object.entries` does not enumerate it, so without a JSON-safe stand-in the
|
|
92
|
+
* normalization pass would flatten `{ by, reason }` into two ordinary leaf
|
|
93
|
+
* paths instead of keeping the marker recognizable as one value.
|
|
94
|
+
*/
|
|
95
|
+
export const HELD_ELSEWHERE_TAG = "chant:heldElsewhere" as const;
|
|
96
|
+
|
|
97
|
+
/** The normalized, JSON-safe form of a {@link HeldElsewhereMarker}. */
|
|
98
|
+
export interface NormalizedHeldElsewhere extends HeldElsewhereOptions {
|
|
99
|
+
readonly heldElsewhere: typeof HELD_ELSEWHERE_TAG;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Normalize a runtime marker to its JSON-safe tagged form. */
|
|
103
|
+
export function normalizeHeldElsewhere(marker: HeldElsewhereMarker): NormalizedHeldElsewhere {
|
|
104
|
+
return { heldElsewhere: HELD_ELSEWHERE_TAG, by: marker.by, reason: marker.reason };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** True when `value` is the normalized, JSON-safe tagged form of a held marker. */
|
|
108
|
+
export function isNormalizedHeldElsewhere(value: unknown): value is NormalizedHeldElsewhere {
|
|
109
|
+
return (
|
|
110
|
+
typeof value === "object" &&
|
|
111
|
+
value !== null &&
|
|
112
|
+
(value as { heldElsewhere?: unknown }).heldElsewhere === HELD_ELSEWHERE_TAG
|
|
113
|
+
);
|
|
114
|
+
}
|