@intentius/chant 0.46.0 → 0.50.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 +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +30 -3
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +9 -2
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- 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.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts +3 -3
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +12 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/explain.d.ts +6 -0
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +14 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/reporters/stylish.d.ts +15 -1
- package/dist/cli/reporters/stylish.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +4 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts +2 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/ensure-secret.d.ts +50 -0
- package/dist/components/verbs/ensure-secret.d.ts.map +1 -0
- package/dist/components/verbs/index.d.ts +13 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts +76 -0
- package/dist/components/verbs/r2-sync.d.ts.map +1 -0
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/components/verbs/wrangler.d.ts +108 -0
- package/dist/components/verbs/wrangler.d.ts.map +1 -0
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/deep-observation.d.ts +14 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/effect-receipt.d.ts +177 -0
- package/dist/effect-receipt.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +20 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +137 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +33 -5
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/observation-baseline.d.ts +21 -3
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/receipt-plan.d.ts +62 -0
- package/dist/lifecycle/receipt-plan.d.ts.map +1 -0
- package/dist/lifecycle/release-ledger.d.ts +20 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lifecycle/teardown.d.ts +6 -4
- package/dist/lifecycle/teardown.d.ts.map +1 -1
- package/dist/lifecycle/unobserved-gate.d.ts +67 -0
- package/dist/lifecycle/unobserved-gate.d.ts.map +1 -0
- package/dist/lint/knowledge-checks.d.ts +48 -0
- package/dist/lint/knowledge-checks.d.ts.map +1 -0
- package/dist/lint/output-checks.d.ts +5 -0
- package/dist/lint/output-checks.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/pipeline-change-gate.d.ts +101 -0
- package/dist/lint/pipeline-change-gate.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +41 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/receipt-checks.d.ts +9 -0
- package/dist/lint/receipt-checks.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lint/rules/cor022-receipt-leaf.d.ts +13 -0
- package/dist/lint/rules/cor022-receipt-leaf.d.ts.map +1 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts +3 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts.map +1 -0
- package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
- package/dist/lint/rules/index.d.ts +3 -1
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/okf-read.d.ts +78 -0
- package/dist/okf-read.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +140 -3
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +18 -2
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +2 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/receipt-store.d.ts +138 -0
- package/dist/op/receipt-store.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +49 -2
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/secret-materialization.d.ts +138 -0
- package/dist/secret-materialization.d.ts.map +1 -0
- package/dist/secret-provenance.d.ts +218 -0
- package/dist/secret-provenance.d.ts.map +1 -0
- package/dist/serializer.d.ts +29 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.test.ts +57 -0
- package/src/audit/core.ts +0 -0
- package/src/audit/detect-bundle.test.ts +1 -1
- package/src/audit/discover.test.ts +24 -0
- package/src/audit/discover.ts +40 -4
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +13 -1
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.test.ts +41 -0
- package/src/build.ts +39 -6
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/audit-fountain/agents/fleet.yaml +27 -0
- package/src/cli/commands/__fixtures__/audit-fountain/k8s/deploy.yaml +16 -0
- package/src/cli/commands/__fixtures__/audit-fountain-clean/fleet.yaml +20 -0
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +268 -1
- package/src/cli/commands/audit.ts +87 -18
- package/src/cli/commands/build.test.ts +245 -0
- package/src/cli/commands/build.ts +210 -21
- package/src/cli/commands/lint.ts +15 -3
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/explain.test.ts +70 -1
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +12 -3
- package/src/cli/handlers/lifecycle.test.ts +115 -1
- package/src/cli/handlers/lifecycle.ts +96 -15
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +72 -1
- package/src/cli/mcp/resource-handlers.ts +38 -1
- package/src/cli/mcp/server.test.ts +323 -3
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/tools/explain.ts +51 -2
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/plugins.ts +4 -2
- package/src/cli/registry.ts +14 -2
- package/src/cli/reporters/stylish.test.ts +154 -0
- package/src/cli/reporters/stylish.ts +154 -33
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/auto-release.ts +6 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/registry.test.ts +7 -2
- package/src/components/starter-plugin.ts +17 -0
- package/src/components/verbs/ensure-secret.test.ts +130 -0
- package/src/components/verbs/ensure-secret.ts +79 -0
- package/src/components/verbs/index.ts +13 -0
- package/src/components/verbs/r2-sync.test.ts +107 -0
- package/src/components/verbs/r2-sync.ts +124 -0
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/components/verbs/wrangler.test.ts +170 -0
- package/src/components/verbs/wrangler.ts +241 -0
- package/src/composite.ts +31 -2
- package/src/config.test.ts +15 -0
- package/src/config.ts +30 -0
- package/src/deep-observation.test.ts +19 -0
- package/src/deep-observation.ts +17 -0
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/effect-receipt-exclusion.test.ts +190 -0
- package/src/effect-receipt.test.ts +419 -0
- package/src/effect-receipt.ts +412 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +95 -6
- package/src/fold/subset.ts +66 -2
- package/src/index.ts +4 -0
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +151 -5
- package/src/lifecycle/change-set.ts +46 -7
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +6 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/observation-baseline.test.ts +46 -0
- package/src/lifecycle/observation-baseline.ts +33 -1
- package/src/lifecycle/receipt-plan.test.ts +250 -0
- package/src/lifecycle/receipt-plan.ts +249 -0
- package/src/lifecycle/release-ledger.ts +20 -0
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lifecycle/teardown.test.ts +31 -0
- package/src/lifecycle/teardown.ts +6 -4
- package/src/lifecycle/unobserved-gate.test.ts +109 -0
- package/src/lifecycle/unobserved-gate.ts +102 -0
- package/src/lint/knowledge-checks.test.ts +80 -0
- package/src/lint/knowledge-checks.ts +74 -0
- package/src/lint/output-checks.test.ts +85 -0
- package/src/lint/output-checks.ts +99 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/pipeline-change-gate.test.ts +144 -0
- package/src/lint/pipeline-change-gate.ts +153 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +60 -0
- package/src/lint/receipt-checks.test.ts +101 -0
- package/src/lint/receipt-checks.ts +93 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/cor022-receipt-leaf.test.ts +116 -0
- package/src/lint/rules/cor022-receipt-leaf.ts +130 -0
- package/src/lint/rules/cor024-receipt-secret-pointer.test.ts +121 -0
- package/src/lint/rules/cor024-receipt-secret-pointer.ts +218 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +35 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +7 -0
- package/src/lint/rules/index.ts +7 -1
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/okf-read.test.ts +149 -0
- package/src/okf-read.ts +197 -0
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +198 -6
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/effect-step.test.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +40 -3
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +212 -29
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/op.test.ts +25 -2
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/receipt-store.ts +211 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +51 -2
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/secret-materialization.test.ts +199 -0
- package/src/secret-materialization.ts +235 -0
- package/src/secret-provenance.test.ts +388 -0
- package/src/secret-provenance.ts +475 -0
- package/src/serializer.ts +30 -0
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
- package/src/yaml.test.ts +88 -0
- package/src/yaml.ts +76 -6
|
@@ -184,6 +184,71 @@ export const testEntity = {
|
|
|
184
184
|
}
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
+
test("#1083 — --fold-rank ranks blockers and, given a path, writes a collapsed-format export", async () => {
|
|
188
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
189
|
+
const runtimePath = resolvePath(thisDir, "../../runtime");
|
|
190
|
+
|
|
191
|
+
// A pair of files that fold fine (same shape as the #1022 test above),
|
|
192
|
+
// so the build produces output and the "no output" guard doesn't fire —
|
|
193
|
+
// unrelated to the ranking below.
|
|
194
|
+
await writeFile(
|
|
195
|
+
join(testDir, "resources.ts"),
|
|
196
|
+
`
|
|
197
|
+
import { createResource } from ${JSON.stringify(runtimePath)};
|
|
198
|
+
export const Bucket = createResource("Test::Bucket", "aws", { arn: "Arn" });
|
|
199
|
+
`,
|
|
200
|
+
);
|
|
201
|
+
await writeFile(
|
|
202
|
+
join(testDir, "main.ts"),
|
|
203
|
+
`
|
|
204
|
+
import { Bucket } from "./resources";
|
|
205
|
+
export const bucket = new Bucket({ name: "my-bucket" });
|
|
206
|
+
`,
|
|
207
|
+
);
|
|
208
|
+
// a.ts fails directly (process.env); b.ts fails because it imports a.ts.
|
|
209
|
+
await writeFile(join(testDir, "a.ts"), `export const a = process.env.SOMETHING;`);
|
|
210
|
+
await writeFile(
|
|
211
|
+
join(testDir, "b.ts"),
|
|
212
|
+
`
|
|
213
|
+
import { a } from "./a";
|
|
214
|
+
export const b = a;
|
|
215
|
+
`,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const awsSerializer: Serializer = {
|
|
219
|
+
name: "aws",
|
|
220
|
+
rulePrefix: "TEST",
|
|
221
|
+
serialize: (entities) => JSON.stringify([...entities.keys()]),
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const collapsedPath = join(testDir, "out", "fold.collapsed");
|
|
225
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
226
|
+
try {
|
|
227
|
+
const result = await buildCommand({
|
|
228
|
+
path: testDir,
|
|
229
|
+
format: "json",
|
|
230
|
+
serializers: [awsSerializer],
|
|
231
|
+
fold: true,
|
|
232
|
+
foldRank: true,
|
|
233
|
+
foldRankCollapsedFile: collapsedPath,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
expect(result.success).toBe(true);
|
|
237
|
+
|
|
238
|
+
const lines = errorSpy.mock.calls.map((call) => String(call[0]));
|
|
239
|
+
const rankingLine = lines.find((l) => l.includes("fold-rank:") && l.includes("blocker(s)"));
|
|
240
|
+
expect(rankingLine).toBeDefined();
|
|
241
|
+
const aLine = lines.find((l) => l.includes("retained 2") && l.includes("a.ts"));
|
|
242
|
+
expect(aLine).toBeDefined();
|
|
243
|
+
|
|
244
|
+
expect(existsSync(collapsedPath)).toBe(true);
|
|
245
|
+
const collapsed = readFileSync(collapsedPath, "utf-8").trim().split("\n");
|
|
246
|
+
expect(collapsed.sort()).toEqual(["fold;a.ts 1", "fold;a.ts;b.ts 1"].sort());
|
|
247
|
+
} finally {
|
|
248
|
+
errorSpy.mockRestore();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
187
252
|
describe("#1396 — ownership.env resolves from the env build parameter", () => {
|
|
188
253
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
189
254
|
const runtimePath = resolvePath(thisDir, "../../runtime");
|
|
@@ -705,6 +770,186 @@ export const testEntity = {
|
|
|
705
770
|
expect(existsSync(join(testDir, "dist", "ops", "alb-deploy", "worker.ts"))).toBe(true);
|
|
706
771
|
});
|
|
707
772
|
|
|
773
|
+
describe("committed-encrypted secret sidecars", () => {
|
|
774
|
+
const CIPHERTEXT = 'apiVersion: v1\nkind: Secret\nstringData:\n T: ENC[AES256_GCM,data:xx]\nsops:\n version: 3.9.4\n';
|
|
775
|
+
|
|
776
|
+
/** A serializer standing in for the k8s one: a sidecar plus a primary doc. */
|
|
777
|
+
const sidecarSerializer: Serializer = {
|
|
778
|
+
name: "multi",
|
|
779
|
+
rulePrefix: "MULTI",
|
|
780
|
+
serialize: () => ({
|
|
781
|
+
primary: "kind: Deployment\n",
|
|
782
|
+
files: { "db.sops.yaml": CIPHERTEXT },
|
|
783
|
+
verbatimFiles: ["db.sops.yaml"],
|
|
784
|
+
}),
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
async function writeProject(fileValue: string): Promise<void> {
|
|
788
|
+
const modulePath = resolvePath(
|
|
789
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
790
|
+
"..",
|
|
791
|
+
"..",
|
|
792
|
+
"secret-provenance",
|
|
793
|
+
);
|
|
794
|
+
await writeFile(
|
|
795
|
+
join(testDir, "infra.ts"),
|
|
796
|
+
`import { declareSecret } from ${JSON.stringify(modulePath)};
|
|
797
|
+
export const dbCredentials = declareSecret({ name: "db-credentials", provenance: "committed-encrypted", file: ${JSON.stringify(fileValue)} });
|
|
798
|
+
export const x = { [Symbol.for("chant.declarable")]: true, entityType: "X", lexicon: "multi", kind: "resource", props: {}, attributes: {} };`,
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
test("a build with no --output refuses, naming the flag", async () => {
|
|
803
|
+
await writeProject("secrets/db.sops.yaml");
|
|
804
|
+
|
|
805
|
+
const result = await buildCommand({
|
|
806
|
+
path: testDir,
|
|
807
|
+
format: "yaml",
|
|
808
|
+
serializers: [sidecarSerializer],
|
|
809
|
+
// no `output` — the sidecar would be echoed to stderr and lost
|
|
810
|
+
} as BuildOptions);
|
|
811
|
+
|
|
812
|
+
expect(result.success).toBe(false);
|
|
813
|
+
expect(result.errors.join("\n")).toContain("--output");
|
|
814
|
+
expect(result.errors.join("\n")).toContain("db.sops.yaml");
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
test("with --output the sidecar lands beside the primary, byte for byte", async () => {
|
|
818
|
+
await writeProject("secrets/db.sops.yaml");
|
|
819
|
+
const outputPath = join(testDir, "dist", "manifests.yaml");
|
|
820
|
+
|
|
821
|
+
const result = await buildCommand({
|
|
822
|
+
path: testDir,
|
|
823
|
+
output: outputPath,
|
|
824
|
+
format: "yaml",
|
|
825
|
+
serializers: [sidecarSerializer],
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
expect(result.errors).toEqual([]);
|
|
829
|
+
expect(readFileSync(join(testDir, "dist", "db.sops.yaml"), "utf-8")).toBe(CIPHERTEXT);
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test("a declared sidecar is never round-tripped through JSON.parse", async () => {
|
|
833
|
+
// YAML is a JSON superset, so a ciphertext file that happens to parse as
|
|
834
|
+
// JSON would otherwise be rewritten key-sorted (or re-emitted as YAML),
|
|
835
|
+
// breaking the `sops` MAC. Declared files skip that path entirely.
|
|
836
|
+
const jsonish = '{"b": 1, "a": 2}\n';
|
|
837
|
+
const jsonSidecar: Serializer = {
|
|
838
|
+
name: "multi",
|
|
839
|
+
rulePrefix: "MULTI",
|
|
840
|
+
serialize: () => ({
|
|
841
|
+
primary: "kind: Deployment\n",
|
|
842
|
+
files: { "db.sops.yaml": jsonish },
|
|
843
|
+
verbatimFiles: ["db.sops.yaml"],
|
|
844
|
+
}),
|
|
845
|
+
};
|
|
846
|
+
await writeProject("secrets/db.sops.yaml");
|
|
847
|
+
const outputPath = join(testDir, "dist", "manifests.yaml");
|
|
848
|
+
|
|
849
|
+
const result = await buildCommand({
|
|
850
|
+
path: testDir,
|
|
851
|
+
output: outputPath,
|
|
852
|
+
format: "yaml",
|
|
853
|
+
serializers: [jsonSidecar],
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
expect(result.errors).toEqual([]);
|
|
857
|
+
expect(readFileSync(join(testDir, "dist", "db.sops.yaml"), "utf-8")).toBe(jsonish);
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
describe("SerializerResult.verbatimFiles — general channel (#1937)", () => {
|
|
862
|
+
test("a verbatim file whose content happens to be valid JSON survives byte-identical", async () => {
|
|
863
|
+
// Deliberately not key-sorted or 2-space indented — content the JSON
|
|
864
|
+
// round trip below would reformat. The old workaround (skip the round
|
|
865
|
+
// trip only because a file happens to fail JSON.parse, true for the
|
|
866
|
+
// committed-encrypted `.sops.yaml` case but never a real guarantee)
|
|
867
|
+
// could not protect this case. `verbatimFiles` can, structurally.
|
|
868
|
+
const raw = '{"b":1,"a":2, "nested":{"z":true}}';
|
|
869
|
+
const verbatimSerializer: Serializer = {
|
|
870
|
+
name: "multi",
|
|
871
|
+
rulePrefix: "MULTI",
|
|
872
|
+
serialize: () => ({
|
|
873
|
+
primary: "{}",
|
|
874
|
+
files: { "raw.json": raw },
|
|
875
|
+
verbatimFiles: ["raw.json"],
|
|
876
|
+
}),
|
|
877
|
+
};
|
|
878
|
+
await writeFile(
|
|
879
|
+
join(testDir, "infra.ts"),
|
|
880
|
+
`export const x = { [Symbol.for("chant.declarable")]: true, entityType: "X", lexicon: "multi", kind: "resource", props: {}, attributes: {} };`,
|
|
881
|
+
);
|
|
882
|
+
const outputPath = join(testDir, "dist", "manifest.json");
|
|
883
|
+
|
|
884
|
+
const result = await buildCommand({
|
|
885
|
+
path: testDir,
|
|
886
|
+
output: outputPath,
|
|
887
|
+
format: "json",
|
|
888
|
+
serializers: [verbatimSerializer],
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
expect(result.errors).toEqual([]);
|
|
892
|
+
expect(readFileSync(join(testDir, "dist", "raw.json"), "utf-8")).toBe(raw);
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
describe("additional-file basename collisions across lexicons (#1937)", () => {
|
|
897
|
+
const lexASerializer = (content: string): Serializer => ({
|
|
898
|
+
name: "lexa",
|
|
899
|
+
rulePrefix: "LEXA",
|
|
900
|
+
serialize: () => ({ primary: "{}", files: { "shared.yaml": content } }),
|
|
901
|
+
});
|
|
902
|
+
const lexBSerializer = (content: string): Serializer => ({
|
|
903
|
+
name: "lexb",
|
|
904
|
+
rulePrefix: "LEXB",
|
|
905
|
+
serialize: () => ({ primary: "{}", files: { "shared.yaml": content } }),
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
async function writeTwoLexiconProject(): Promise<void> {
|
|
909
|
+
await writeFile(
|
|
910
|
+
join(testDir, "infra.ts"),
|
|
911
|
+
[
|
|
912
|
+
`export const a = { [Symbol.for("chant.declarable")]: true, entityType: "A", lexicon: "lexa", kind: "resource", props: {}, attributes: {} };`,
|
|
913
|
+
`export const b = { [Symbol.for("chant.declarable")]: true, entityType: "B", lexicon: "lexb", kind: "resource", props: {}, attributes: {} };`,
|
|
914
|
+
].join("\n"),
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
test("differing content at the same basename fails the build, naming the basename and both sources", async () => {
|
|
919
|
+
await writeTwoLexiconProject();
|
|
920
|
+
const outputPath = join(testDir, "dist", "manifest.json");
|
|
921
|
+
|
|
922
|
+
const result = await buildCommand({
|
|
923
|
+
path: testDir,
|
|
924
|
+
output: outputPath,
|
|
925
|
+
format: "json",
|
|
926
|
+
serializers: [lexASerializer("from lexa\n"), lexBSerializer("from lexb\n")],
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
expect(result.success).toBe(false);
|
|
930
|
+
const message = result.errors.join("\n");
|
|
931
|
+
expect(message).toContain("shared.yaml");
|
|
932
|
+
expect(message).toContain("lexa");
|
|
933
|
+
expect(message).toContain("lexb");
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
test("identical content at the same basename passes", async () => {
|
|
937
|
+
await writeTwoLexiconProject();
|
|
938
|
+
const outputPath = join(testDir, "dist", "manifest.json");
|
|
939
|
+
const shared = "same content for both\n";
|
|
940
|
+
|
|
941
|
+
const result = await buildCommand({
|
|
942
|
+
path: testDir,
|
|
943
|
+
output: outputPath,
|
|
944
|
+
format: "json",
|
|
945
|
+
serializers: [lexASerializer(shared), lexBSerializer(shared)],
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
expect(result.errors).toEqual([]);
|
|
949
|
+
expect(readFileSync(join(testDir, "dist", "shared.yaml"), "utf-8")).toBe(shared);
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
|
|
708
953
|
test("op worker files go to <project>/dist/ops even with no --output (#878)", async () => {
|
|
709
954
|
// The generated Op worker must land where `chant run <op> --temporal` reads it
|
|
710
955
|
// (`<project>/dist/ops/<name>/worker.ts`) even when the build has no --output
|
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
isOwnershipParamRef,
|
|
8
8
|
resolveFoldEnabled,
|
|
9
9
|
resolveSandboxEnabled,
|
|
10
|
+
resolveKnowledgeDir,
|
|
10
11
|
} from "../../config";
|
|
12
|
+
import { loadOkfBundle } from "../../okf-read";
|
|
11
13
|
import { unknownEnvError } from "../../env";
|
|
12
14
|
import type { OwnershipMarker } from "../../ownership";
|
|
13
15
|
import { resolveCliBuildParams } from "../build-params-cli";
|
|
@@ -15,10 +17,14 @@ import type { Serializer, SerializerResult } from "../../serializer";
|
|
|
15
17
|
import type { LexiconPlugin } from "../../lexicon";
|
|
16
18
|
import { resolveLexiconVersions, collectBuildRootContributors } from "../plugins";
|
|
17
19
|
import { runPostSynthChecks } from "../../lint/post-synth";
|
|
20
|
+
import { coreReceiptChecks } from "../../lint/receipt-checks";
|
|
21
|
+
import { coreOutputChecks } from "../../lint/output-checks";
|
|
22
|
+
import { coreKnowledgeChecks } from "../../lint/knowledge-checks";
|
|
18
23
|
import { applyConfiguredSeverity } from "../../lint/config";
|
|
19
24
|
import { loadPolicyChecks } from "../../lint/policy";
|
|
20
25
|
import { armSandboxPolicyExecution, runProjectPolicies } from "../../lint/policy-sandbox";
|
|
21
26
|
import { sortedJsonReplacer } from "../../utils";
|
|
27
|
+
import { rankFoldBlockers, toCollapsedFormat, type FoldRankResult } from "../../discovery/fold-rank";
|
|
22
28
|
import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format";
|
|
23
29
|
import { writeFileSync, mkdirSync } from "fs";
|
|
24
30
|
import { resolve, dirname, join, relative } from "path";
|
|
@@ -79,6 +85,23 @@ export interface BuildOptions {
|
|
|
79
85
|
* after {@link params}.
|
|
80
86
|
*/
|
|
81
87
|
paramsFile?: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* chant #1083 — `chant build --fold --fold-rank`: after the build, rank
|
|
91
|
+
* `"run"`-mode files by dominator retained-count over the forward
|
|
92
|
+
* import-failure graph (see `../../discovery/fold-rank.ts`), and report
|
|
93
|
+
* the reverse-taint bucket (#1044) separately. Printed to stderr,
|
|
94
|
+
* independent of `--verbose`'s per-file listing. No-op without `fold`.
|
|
95
|
+
*/
|
|
96
|
+
foldRank?: boolean;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* chant #1083 — `chant build --fold --fold-rank <path>`: as {@link
|
|
100
|
+
* foldRank}, and ALSO writes the ranking as a Brendan Gregg collapsed
|
|
101
|
+
* stack export (weighted by retained count) to this path, so it opens in
|
|
102
|
+
* any flame/icicle viewer with no chant-specific tooling.
|
|
103
|
+
*/
|
|
104
|
+
foldRankCollapsedFile?: string;
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
/**
|
|
@@ -341,6 +364,23 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
341
364
|
}
|
|
342
365
|
}
|
|
343
366
|
|
|
367
|
+
// chant #1083 — rank fold blockers by dominator retained-count over the
|
|
368
|
+
// forward import-failure graph, and report the reverse-taint bucket
|
|
369
|
+
// (#1044) separately. Opt-in (`--fold-rank`) and independent of
|
|
370
|
+
// `--verbose`: the per-file listing above says WHAT ran; this says WHICH
|
|
371
|
+
// of those files is worth fixing first.
|
|
372
|
+
if (fold && (options.foldRank || options.foldRankCollapsedFile) && result.foldDecisions.length > 0) {
|
|
373
|
+
const ranking = await rankFoldBlockers(result.foldDecisions);
|
|
374
|
+
console.error(formatInfo(formatFoldRanking(ranking, infraPath)));
|
|
375
|
+
if (options.foldRankCollapsedFile) {
|
|
376
|
+
const collapsedPath = resolve(options.foldRankCollapsedFile);
|
|
377
|
+
mkdirSync(dirname(collapsedPath), { recursive: true });
|
|
378
|
+
const lines = toCollapsedFormat(ranking, { relativeTo: infraPath });
|
|
379
|
+
writeFileSync(collapsedPath, lines.length > 0 ? `${lines.join("\n")}\n` : "");
|
|
380
|
+
console.error(formatInfo(`[fold-rank] collapsed-format export: ${collapsedPath} (${lines.length} line(s))`));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
344
384
|
// Format errors — grouped by message, because one refusal can surface once
|
|
345
385
|
// per resource file. A project whose params module throws (a guard like
|
|
346
386
|
// "this seam cannot back that tier") throws in EVERY file that imports it,
|
|
@@ -382,6 +422,66 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
382
422
|
// finding just as it suppresses a pre-synth one. A finding it suppresses is
|
|
383
423
|
// counted, not dropped silently — see `suppressedPostSynthCount` below.
|
|
384
424
|
let suppressedPostSynthCount = 0;
|
|
425
|
+
|
|
426
|
+
// Core-owned post-synth checks over effect receipts (#1833). Receipts are
|
|
427
|
+
// recognized by marker, lexicon-independently, so this set runs over the
|
|
428
|
+
// FULL build result — unscoped, and regardless of which plugins loaded.
|
|
429
|
+
// Same severity-resolution funnel as every other post-synth finding.
|
|
430
|
+
if (result.errors.length === 0) {
|
|
431
|
+
const receiptDiags = runPostSynthChecks(coreReceiptChecks(), result, env);
|
|
432
|
+
const { diagnostics: activeDiags, suppressed } = applyConfiguredSeverity(receiptDiags, config.lint?.rules);
|
|
433
|
+
suppressedPostSynthCount += suppressed.length;
|
|
434
|
+
for (const diag of activeDiags) {
|
|
435
|
+
const prefix = diag.entity ? `[${diag.entity}] ` : "";
|
|
436
|
+
const lexiconSuffix = diag.lexicon ? ` (${diag.lexicon})` : "";
|
|
437
|
+
if (diag.severity === "error") {
|
|
438
|
+
errors.push(formatError({ message: `${prefix}${diag.message}${lexiconSuffix}` }));
|
|
439
|
+
} else {
|
|
440
|
+
warnings.push(formatWarning({ message: `${prefix}${diag.message}${lexiconSuffix}` }));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Core-owned post-synth check over serialized output text (#1526). The
|
|
446
|
+
// "[object Object]" marker means a reference got stringified instead of
|
|
447
|
+
// resolved; it can appear in any lexicon's emitted text, so this runs over
|
|
448
|
+
// the FULL build result the same way the receipt checks above do.
|
|
449
|
+
if (result.errors.length === 0) {
|
|
450
|
+
const outputDiags = runPostSynthChecks(coreOutputChecks(), result, env);
|
|
451
|
+
const { diagnostics: activeDiags, suppressed } = applyConfiguredSeverity(outputDiags, config.lint?.rules);
|
|
452
|
+
suppressedPostSynthCount += suppressed.length;
|
|
453
|
+
for (const diag of activeDiags) {
|
|
454
|
+
const prefix = diag.entity ? `[${diag.entity}] ` : "";
|
|
455
|
+
const lexiconSuffix = diag.lexicon ? ` (${diag.lexicon})` : "";
|
|
456
|
+
if (diag.severity === "error") {
|
|
457
|
+
errors.push(formatError({ message: `${prefix}${diag.message}${lexiconSuffix}` }));
|
|
458
|
+
} else {
|
|
459
|
+
warnings.push(formatWarning({ message: `${prefix}${diag.message}${lexiconSuffix}` }));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Core-owned post-synth check over the project's OKF knowledge bundle
|
|
465
|
+
// (#1865, design #1059): a concept's `binds` naming no discovered entity is
|
|
466
|
+
// a stale binding, surfaced as a COR026 warning. `resolveKnowledgeDir` +
|
|
467
|
+
// `loadOkfBundle` never fail — a missing/absent `knowledge/` directory
|
|
468
|
+
// loads an empty bundle, so this is a no-op for a project that has none.
|
|
469
|
+
if (result.errors.length === 0) {
|
|
470
|
+
const bundle = await loadOkfBundle(resolveKnowledgeDir(config, configDir));
|
|
471
|
+
const knowledgeDiags = runPostSynthChecks(coreKnowledgeChecks(bundle), result, env);
|
|
472
|
+
const { diagnostics: activeDiags, suppressed } = applyConfiguredSeverity(knowledgeDiags, config.lint?.rules);
|
|
473
|
+
suppressedPostSynthCount += suppressed.length;
|
|
474
|
+
for (const diag of activeDiags) {
|
|
475
|
+
const prefix = diag.entity ? `[${diag.entity}] ` : "";
|
|
476
|
+
const lexiconSuffix = diag.lexicon ? ` (${diag.lexicon})` : "";
|
|
477
|
+
if (diag.severity === "error") {
|
|
478
|
+
errors.push(formatError({ message: `${prefix}${diag.message}${lexiconSuffix}` }));
|
|
479
|
+
} else {
|
|
480
|
+
warnings.push(formatWarning({ message: `${prefix}${diag.message}${lexiconSuffix}` }));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
385
485
|
if (result.errors.length === 0 && options.plugins) {
|
|
386
486
|
for (const plugin of options.plugins) {
|
|
387
487
|
if (!plugin.postSynthChecks) continue;
|
|
@@ -479,14 +579,55 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
479
579
|
|
|
480
580
|
// Handle output
|
|
481
581
|
if (result.errors.length === 0 && errors.length === 0) {
|
|
482
|
-
// Extract primary content and collect additional files from SerializerResult
|
|
483
|
-
|
|
582
|
+
// Extract primary content and collect additional files from SerializerResult.
|
|
583
|
+
// Each entry remembers which lexicon produced it, so a same-basename
|
|
584
|
+
// collision between two sources (below) can name both.
|
|
585
|
+
const additionalFiles = new Map<string, { content: string; source: string }>();
|
|
586
|
+
|
|
587
|
+
// Basenames (from some SerializerResult.files) that must reach disk
|
|
588
|
+
// BYTE-FOR-BYTE — e.g. committed ciphertext
|
|
589
|
+
// (`declareSecret({ provenance: "committed-encrypted", file })`). The
|
|
590
|
+
// additional-file writer below round-trips anything JSON.parse accepts,
|
|
591
|
+
// key-sorting it and possibly re-emitting it as YAML, which would break
|
|
592
|
+
// e.g. the `sops` MAC on committed ciphertext. A serializer opts a file
|
|
593
|
+
// out of that round trip structurally, via `SerializerResult.verbatimFiles`
|
|
594
|
+
// (chant#1937) — "the parse happens to fail" is not a safety guarantee.
|
|
595
|
+
const verbatimFiles = new Set<string>();
|
|
596
|
+
|
|
597
|
+
// A same basename emitted by two sources with different content would
|
|
598
|
+
// otherwise silently overwrite (last-writer-wins, since `additionalFiles`
|
|
599
|
+
// is keyed by basename alone). Identical content from two sources is
|
|
600
|
+
// fine — dedup — but differing content is refused, naming both sources,
|
|
601
|
+
// the same way the committed-encrypted resolution stage refuses a
|
|
602
|
+
// basename collision among its own declarations.
|
|
603
|
+
function addAdditionalFile(filename: string, content: string, source: string): void {
|
|
604
|
+
const existing = additionalFiles.get(filename);
|
|
605
|
+
if (existing === undefined) {
|
|
606
|
+
additionalFiles.set(filename, { content, source });
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (existing.content === content) return;
|
|
610
|
+
errors.push(
|
|
611
|
+
formatError({
|
|
612
|
+
message:
|
|
613
|
+
`Additional file "${filename}" collides: "${existing.source}" and "${source}" ` +
|
|
614
|
+
`both emit it with different content — additional files share one flat ` +
|
|
615
|
+
`namespace, so two sources whose filenames match would otherwise silently ` +
|
|
616
|
+
`overwrite each other.`,
|
|
617
|
+
}),
|
|
618
|
+
);
|
|
619
|
+
}
|
|
484
620
|
|
|
485
|
-
function getPrimaryContent(raw: string | SerializerResult): string {
|
|
621
|
+
function getPrimaryContent(raw: string | SerializerResult, source: string): string {
|
|
486
622
|
if (typeof raw === "string") return raw;
|
|
487
623
|
if (raw.files) {
|
|
488
624
|
for (const [filename, content] of Object.entries(raw.files)) {
|
|
489
|
-
|
|
625
|
+
addAdditionalFile(filename, content, source);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (raw.verbatimFiles) {
|
|
629
|
+
for (const filename of raw.verbatimFiles) {
|
|
630
|
+
verbatimFiles.add(filename);
|
|
490
631
|
}
|
|
491
632
|
}
|
|
492
633
|
return raw.primary;
|
|
@@ -505,8 +646,8 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
505
646
|
// Multiple lexicons: wrap in lexicon keys
|
|
506
647
|
let output: string = "{}";
|
|
507
648
|
if (result.outputs.size === 1) {
|
|
508
|
-
const [, raw] = [...result.outputs.entries()][0];
|
|
509
|
-
const content = getPrimaryContent(raw);
|
|
649
|
+
const [lexiconName, raw] = [...result.outputs.entries()][0];
|
|
650
|
+
const content = getPrimaryContent(raw, lexiconName);
|
|
510
651
|
const parsed = tryParseJson(content);
|
|
511
652
|
if ("json" in parsed) {
|
|
512
653
|
output = JSON.stringify(parsed.json, sortedJsonReplacer, 2);
|
|
@@ -523,7 +664,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
523
664
|
const nonJsonSections: string[] = [];
|
|
524
665
|
const sortedLexiconNames = [...result.outputs.keys()].sort();
|
|
525
666
|
for (const lexiconName of sortedLexiconNames) {
|
|
526
|
-
const content = getPrimaryContent(result.outputs.get(lexiconName)
|
|
667
|
+
const content = getPrimaryContent(result.outputs.get(lexiconName)!, lexiconName);
|
|
527
668
|
const parsed = tryParseJson(content);
|
|
528
669
|
if ("json" in parsed) {
|
|
529
670
|
combined[lexiconName] = parsed.json;
|
|
@@ -554,13 +695,13 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
554
695
|
// `foo.yaml`, so the durable-run worker was never where `run --temporal` looks.
|
|
555
696
|
const projectDist = resolve(options.path ?? ".", "dist");
|
|
556
697
|
let opsWritten = 0;
|
|
557
|
-
for (const [filename,
|
|
698
|
+
for (const [filename, entry] of [...additionalFiles]) {
|
|
558
699
|
if (!filename.startsWith("ops/")) continue;
|
|
559
700
|
additionalFiles.delete(filename);
|
|
560
701
|
try {
|
|
561
702
|
const targetPath = join(projectDist, filename);
|
|
562
703
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
563
|
-
writeFileSync(targetPath, content);
|
|
704
|
+
writeFileSync(targetPath, entry.content);
|
|
564
705
|
opsWritten += 1;
|
|
565
706
|
} catch (err) {
|
|
566
707
|
errors.push(
|
|
@@ -585,17 +726,20 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
585
726
|
// Write additional files (e.g. nested stack templates) alongside the primary output
|
|
586
727
|
if (additionalFiles.size > 0) {
|
|
587
728
|
const outputDir = dirname(outputPath);
|
|
588
|
-
for (const [filename,
|
|
589
|
-
let fileContent = content;
|
|
590
|
-
// Format additional files consistently
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
fileContent =
|
|
729
|
+
for (const [filename, entry] of additionalFiles) {
|
|
730
|
+
let fileContent = entry.content;
|
|
731
|
+
// Format additional files consistently — except the ones declared
|
|
732
|
+
// verbatim, which are copied exactly as they were committed.
|
|
733
|
+
if (!verbatimFiles.has(filename)) {
|
|
734
|
+
try {
|
|
735
|
+
const fileParsed = JSON.parse(entry.content);
|
|
736
|
+
fileContent = JSON.stringify(fileParsed, sortedJsonReplacer, 2);
|
|
737
|
+
if (options.format === "yaml") {
|
|
738
|
+
fileContent = jsonToYaml(JSON.parse(fileContent));
|
|
739
|
+
}
|
|
740
|
+
} catch {
|
|
741
|
+
// If not JSON, write as-is
|
|
596
742
|
}
|
|
597
|
-
} catch {
|
|
598
|
-
// If not JSON, write as-is
|
|
599
743
|
}
|
|
600
744
|
const targetPath = join(outputDir, filename);
|
|
601
745
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
@@ -609,13 +753,28 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
609
753
|
})
|
|
610
754
|
);
|
|
611
755
|
}
|
|
756
|
+
} else if (verbatimFiles.size > 0) {
|
|
757
|
+
// A build carrying committed ciphertext and no --output has nowhere to
|
|
758
|
+
// put the sidecar. Echoing it to a terminal is not useful and dropping
|
|
759
|
+
// it silently is worse: the primary output would reference a Secret
|
|
760
|
+
// whose file never got written, and the miss would surface as a pod
|
|
761
|
+
// failing to start, far from its cause. Refuse, naming the flag.
|
|
762
|
+
const names = [...verbatimFiles].sort().join(", ");
|
|
763
|
+
errors.push(
|
|
764
|
+
formatError({
|
|
765
|
+
message:
|
|
766
|
+
`This build emits committed-encrypted secret file(s) (${names}) as sidecars, ` +
|
|
767
|
+
`which need a directory to be written into. Re-run with --output <path> ` +
|
|
768
|
+
`(the sidecars land beside it).`,
|
|
769
|
+
}),
|
|
770
|
+
);
|
|
612
771
|
} else {
|
|
613
772
|
// Print to stdout
|
|
614
773
|
console.log(output);
|
|
615
774
|
// Log additional files to stderr if any
|
|
616
|
-
for (const [filename,
|
|
775
|
+
for (const [filename, entry] of additionalFiles) {
|
|
617
776
|
console.error(`\n--- ${filename} ---`);
|
|
618
|
-
console.error(content);
|
|
777
|
+
console.error(entry.content);
|
|
619
778
|
}
|
|
620
779
|
}
|
|
621
780
|
}
|
|
@@ -659,6 +818,36 @@ export function summarizeFoldDecisions(decisions: readonly { mode: string }[]):
|
|
|
659
818
|
return `fold: ${files(folded)} folded, ${ran} ran${hint}`;
|
|
660
819
|
}
|
|
661
820
|
|
|
821
|
+
/**
|
|
822
|
+
* `chant build --fold --fold-rank`'s text report (chant #1083): blockers
|
|
823
|
+
* ranked by dominator retained-count, highest first, then the reverse-taint
|
|
824
|
+
* bucket (#1044) called out separately since the tree can't express that
|
|
825
|
+
* edge (see `../../discovery/fold-rank.ts`'s module doc).
|
|
826
|
+
*/
|
|
827
|
+
export function formatFoldRanking(ranking: FoldRankResult, infraPath: string): string {
|
|
828
|
+
const rel = (file: string): string => relative(infraPath, file) || file;
|
|
829
|
+
const lines: string[] = [];
|
|
830
|
+
if (ranking.blockers.length === 0) {
|
|
831
|
+
lines.push("fold-rank: no run-mode files to rank");
|
|
832
|
+
} else {
|
|
833
|
+
lines.push(`fold-rank: ${ranking.blockers.length} blocker(s), ${ranking.totalBlocked} file(s) blocked`);
|
|
834
|
+
for (const b of ranking.blockers) {
|
|
835
|
+
const via = b.topLevel ? "" : ` (behind ${rel(b.dominatedBy!)})`;
|
|
836
|
+
const reason = b.reason ? ` — ${b.reason}` : "";
|
|
837
|
+
lines.push(` retained ${b.retained}: ${rel(b.file)}${via}${reason}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (ranking.reverseTainted.length > 0) {
|
|
841
|
+
lines.push(
|
|
842
|
+
`fold-rank: ${ranking.reverseTainted.length} file(s) held back only by the reverse rule (#1044) — would fold in isolation, blocked by a run-mode importer, not modeled in the tree above:`,
|
|
843
|
+
);
|
|
844
|
+
for (const t of ranking.reverseTainted) {
|
|
845
|
+
lines.push(` ${rel(t.file)}`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return lines.join("\n");
|
|
849
|
+
}
|
|
850
|
+
|
|
662
851
|
/**
|
|
663
852
|
* Simple JSON to YAML converter
|
|
664
853
|
*/
|
package/src/cli/commands/lint.ts
CHANGED
|
@@ -20,8 +20,9 @@ import { GENERATED_MARKER } from "../../discovery/files";
|
|
|
20
20
|
|
|
21
21
|
// Import config loader
|
|
22
22
|
import { loadConfig, resolveRulesForFile, resolveConfiguredSeverity, findProjectRoot } from "../../lint/config";
|
|
23
|
-
import { loadChantConfig } from "../../config";
|
|
23
|
+
import { loadChantConfig, resolveKnowledgeDir } from "../../config";
|
|
24
24
|
import type { LintProjectConfig } from "../../lint/rule";
|
|
25
|
+
import { loadOkfBundle, type OkfBundle } from "../../okf-read";
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Type guard to check if a value conforms to the LintRule interface.
|
|
@@ -454,10 +455,21 @@ export async function lintCommand(options: LintOptions): Promise<LintResult> {
|
|
|
454
455
|
// runLint() call below via LintContext.projectConfig. Best-effort: a
|
|
455
456
|
// directory with no project config lints with those rules silent.
|
|
456
457
|
let projectConfig: LintProjectConfig | undefined;
|
|
458
|
+
// chant #1866 — the loaded OKF knowledge bundle, threaded into
|
|
459
|
+
// `formatStylish` below so its suppressed section can resolve `okf:`
|
|
460
|
+
// citations. Best-effort like `projectConfig` above (same
|
|
461
|
+
// `loadChantConfig` call): a directory with no chant config, or no
|
|
462
|
+
// `knowledge/` bundle, lints with citations rendering unresolved rather
|
|
463
|
+
// than a failure — `loadOkfBundle` already treats a missing directory as
|
|
464
|
+
// an empty bundle.
|
|
465
|
+
let knowledgeBundle: OkfBundle | undefined;
|
|
457
466
|
try {
|
|
458
|
-
|
|
467
|
+
const chantConfig = (await loadChantConfig(projectRoot)).config;
|
|
468
|
+
projectConfig = chantConfig as LintProjectConfig;
|
|
469
|
+
knowledgeBundle = await loadOkfBundle(resolveKnowledgeDir(chantConfig, projectRoot));
|
|
459
470
|
} catch {
|
|
460
471
|
projectConfig = undefined;
|
|
472
|
+
knowledgeBundle = undefined;
|
|
461
473
|
}
|
|
462
474
|
|
|
463
475
|
// Load all rules from lexicon plugins (core "chant" + lexicon-specific)
|
|
@@ -588,7 +600,7 @@ export async function lintCommand(options: LintOptions): Promise<LintResult> {
|
|
|
588
600
|
break;
|
|
589
601
|
case "stylish":
|
|
590
602
|
default:
|
|
591
|
-
output = formatStylish(diagnostics);
|
|
603
|
+
output = formatStylish(diagnostics, suppressed, knowledgeBundle);
|
|
592
604
|
break;
|
|
593
605
|
}
|
|
594
606
|
|
|
@@ -148,6 +148,8 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
|
|
|
148
148
|
env: args.env,
|
|
149
149
|
fold: args.fold,
|
|
150
150
|
sandbox: args.sandbox,
|
|
151
|
+
foldRank: args.foldRank,
|
|
152
|
+
foldRankCollapsedFile: args.foldRankCollapsedFile,
|
|
151
153
|
params,
|
|
152
154
|
paramsFile: args.paramsFile,
|
|
153
155
|
});
|