@intentius/chant-lexicon-cedar 0.44.9 → 0.44.12
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/README.md +48 -2
- package/dist/agentcore/embed.d.ts +189 -0
- package/dist/agentcore/embed.d.ts.map +1 -0
- package/dist/agentcore/enforcement.d.ts +76 -0
- package/dist/agentcore/enforcement.d.ts.map +1 -0
- package/dist/agentcore/scan.d.ts +46 -0
- package/dist/agentcore/scan.d.ts.map +1 -0
- package/dist/avp/client.d.ts +85 -8
- package/dist/avp/client.d.ts.map +1 -1
- package/dist/codegen/docs-dogwood.d.ts +21 -0
- package/dist/codegen/docs-dogwood.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/dogwood/cli.d.ts +41 -0
- package/dist/dogwood/cli.d.ts.map +1 -1
- package/dist/dogwood/index.d.ts +10 -4
- package/dist/dogwood/index.d.ts.map +1 -1
- package/dist/dogwood/replay-activity.d.ts +196 -0
- package/dist/dogwood/replay-activity.d.ts.map +1 -0
- package/dist/dogwood/replay-op.d.ts +165 -0
- package/dist/dogwood/replay-op.d.ts.map +1 -0
- package/dist/dogwood/serialize.d.ts +20 -0
- package/dist/dogwood/serialize.d.ts.map +1 -1
- package/dist/dogwood/trace.d.ts +215 -0
- package/dist/dogwood/trace.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +5 -3
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/dwdc013.d.ts +33 -0
- package/dist/lint/post-synth/dwdc013.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +1 -0
- package/dist/okf/rules/DWDC013.md +15 -0
- package/dist/okf/types/Policy.md +1 -0
- package/dist/op/activities/index.d.ts +18 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/dwdc013.ts +64 -0
- package/dist/skills/chant-cedar-dogwood.md +327 -0
- package/package.json +7 -2
- package/src/agentcore/embed.test.ts +254 -0
- package/src/agentcore/embed.ts +399 -0
- package/src/agentcore/enforcement.test.ts +43 -0
- package/src/agentcore/enforcement.ts +92 -0
- package/src/agentcore/scan.ts +119 -0
- package/src/avp/OWNERSHIP.md +38 -0
- package/src/avp/client.test.ts +271 -0
- package/src/avp/client.ts +150 -16
- package/src/codegen/docs-dogwood.ts +1119 -0
- package/src/codegen/docs.ts +66 -1
- package/src/dogwood/cli.test.ts +122 -1
- package/src/dogwood/cli.ts +122 -1
- package/src/dogwood/index.ts +74 -1
- package/src/dogwood/replay-activity.test.ts +481 -0
- package/src/dogwood/replay-activity.ts +506 -0
- package/src/dogwood/replay-op.ts +242 -0
- package/src/dogwood/serialize.ts +37 -0
- package/src/dogwood/trace.test.ts +231 -0
- package/src/dogwood/trace.ts +471 -0
- package/src/index.ts +52 -0
- package/src/lint/audit-catalog.ts +8 -0
- package/src/lint/post-synth/dwd-post-synth.test.ts +119 -1
- package/src/lint/post-synth/dwdc013.ts +64 -0
- package/src/lint/post-synth/dwde-post-synth.test.ts +1 -1
- package/src/lint/post-synth/index.ts +2 -0
- package/src/op/activities/index.ts +27 -0
- package/src/plugin.test.ts +3 -2
- package/src/plugin.ts +30 -0
- package/src/skills/chant-cedar-dogwood.md +327 -0
package/src/codegen/docs.ts
CHANGED
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
import { dirname, join } from "path";
|
|
22
22
|
import { fileURLToPath } from "url";
|
|
23
23
|
import { docsPipeline, writeDocsSite, type DocsConfig } from "@intentius/chant/codegen/docs";
|
|
24
|
+
import {
|
|
25
|
+
dogwoodEventSchemas,
|
|
26
|
+
dogwoodOverview,
|
|
27
|
+
dogwoodReplay,
|
|
28
|
+
dogwoodTemporalPolicies,
|
|
29
|
+
dogwoodValidation,
|
|
30
|
+
} from "./docs-dogwood";
|
|
24
31
|
|
|
25
32
|
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
26
33
|
|
|
@@ -67,7 +74,14 @@ any evaluator with chant nowhere in sight.
|
|
|
67
74
|
There is no \`cedarGate()\` and there will not be one.
|
|
68
75
|
[Organizational policy](/chant/guide/organizational-policy/) in chant is
|
|
69
76
|
TypeScript post-synth checks; a second policy engine would duplicate the lint
|
|
70
|
-
engine. chant compiles *to* Cedar; it is not governed *by* Cedar
|
|
77
|
+
engine. chant compiles *to* Cedar; it is not governed *by* Cedar.
|
|
78
|
+
|
|
79
|
+
## The dogwood dialect
|
|
80
|
+
|
|
81
|
+
Cedar with temporal operators, shipping inside this lexicon as a pre-release
|
|
82
|
+
surface under the \`DWD\` id family: a policy that can depend on what already
|
|
83
|
+
happened in a session. Start at [The Dogwood Dialect](./dogwood), which is
|
|
84
|
+
honest about upstream's governance before it shows you a builder.`;
|
|
71
85
|
|
|
72
86
|
// ── getting-started ───────────────────────────────────────────────
|
|
73
87
|
|
|
@@ -858,6 +872,57 @@ export async function generateDocs(options?: { verbose?: boolean }): Promise<voi
|
|
|
858
872
|
description: "The AVP statement seam, and the other Cedar evaluators.",
|
|
859
873
|
content: avpPage,
|
|
860
874
|
},
|
|
875
|
+
// The dogwood dialect (#1662). `sidebar: false` on each, because these
|
|
876
|
+
// five belong under one group rather than flat after the cedar pages —
|
|
877
|
+
// the group is declared in `sidebarExtra` below, and `buildSidebar`
|
|
878
|
+
// would otherwise list them twice.
|
|
879
|
+
{
|
|
880
|
+
slug: "dogwood",
|
|
881
|
+
title: "The Dogwood Dialect",
|
|
882
|
+
description: "Cedar with temporal operators — what ships, and what pre-release means here.",
|
|
883
|
+
content: dogwoodOverview,
|
|
884
|
+
sidebar: false,
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
slug: "dogwood-temporal-policies",
|
|
888
|
+
title: "Temporal Policies",
|
|
889
|
+
description: "The typed builders, the parser primitives, and which operators are really macros.",
|
|
890
|
+
content: dogwoodTemporalPolicies,
|
|
891
|
+
sidebar: false,
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
slug: "dogwood-event-schemas",
|
|
895
|
+
title: "Event Schemas",
|
|
896
|
+
description: "The .dwschema surface, the callerPrincipal pin, and what opting out of it widens.",
|
|
897
|
+
content: dogwoodEventSchemas,
|
|
898
|
+
sidebar: false,
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
slug: "dogwood-validation",
|
|
902
|
+
title: "Dogwood Validation",
|
|
903
|
+
description: "The DWD walls that always run, and the CLI-gated checks that need the binary.",
|
|
904
|
+
content: dogwoodValidation,
|
|
905
|
+
sidebar: false,
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
slug: "dogwood-replay",
|
|
909
|
+
title: "Replay",
|
|
910
|
+
description: "Typed traces, PolicyReplayOp, and the both-bags trap that makes half a trace pass.",
|
|
911
|
+
content: dogwoodReplay,
|
|
912
|
+
sidebar: false,
|
|
913
|
+
},
|
|
914
|
+
],
|
|
915
|
+
sidebarExtra: [
|
|
916
|
+
{
|
|
917
|
+
label: "Dogwood",
|
|
918
|
+
items: [
|
|
919
|
+
{ label: "The Dialect", slug: "dogwood" },
|
|
920
|
+
{ label: "Temporal Policies", slug: "dogwood-temporal-policies" },
|
|
921
|
+
{ label: "Event Schemas", slug: "dogwood-event-schemas" },
|
|
922
|
+
{ label: "Validation", slug: "dogwood-validation" },
|
|
923
|
+
{ label: "Replay", slug: "dogwood-replay" },
|
|
924
|
+
],
|
|
925
|
+
},
|
|
861
926
|
],
|
|
862
927
|
};
|
|
863
928
|
|
package/src/dogwood/cli.test.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* sync.
|
|
11
11
|
*/
|
|
12
12
|
import { describe, test, expect, afterEach } from "vitest";
|
|
13
|
-
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
13
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
14
14
|
import { tmpdir } from "os";
|
|
15
15
|
import { join } from "path";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
@@ -20,9 +20,11 @@ import {
|
|
|
20
20
|
findDogwoodBinary,
|
|
21
21
|
formatDogwoodDiagnostic,
|
|
22
22
|
parseLowerOutput,
|
|
23
|
+
parseReplayOutput,
|
|
23
24
|
parseValidateOutput,
|
|
24
25
|
resetDogwoodCli,
|
|
25
26
|
runDogwoodLower,
|
|
27
|
+
runDogwoodReplay,
|
|
26
28
|
runDogwoodValidate,
|
|
27
29
|
type DogwoodRun,
|
|
28
30
|
type DogwoodRunner,
|
|
@@ -261,6 +263,125 @@ describe("parseLowerOutput", () => {
|
|
|
261
263
|
});
|
|
262
264
|
});
|
|
263
265
|
|
|
266
|
+
// ── replay ─────────────────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
describe("parseReplayOutput", () => {
|
|
269
|
+
/** The stream #1657 recorded for `read_after_login`, exit 0. */
|
|
270
|
+
const RECORDED = {
|
|
271
|
+
verdicts: [
|
|
272
|
+
{ index: 0, timestamp: 0, verdict: "deny", determining_rules: [], errors: [] },
|
|
273
|
+
{ index: 1, timestamp: 10, verdict: "allow", determining_rules: [0], errors: [] },
|
|
274
|
+
{ index: 2, timestamp: 7200, verdict: "deny", determining_rules: [], errors: [] },
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
test("reads the verdict stream, rules included", () => {
|
|
279
|
+
const result = parseReplayOutput(json(RECORDED));
|
|
280
|
+
expect(result.kind).toBe("replayed");
|
|
281
|
+
if (result.kind !== "replayed") return;
|
|
282
|
+
expect(result.verdicts).toHaveLength(3);
|
|
283
|
+
expect(result.verdicts[1]).toEqual({
|
|
284
|
+
index: 1,
|
|
285
|
+
timestamp: 10,
|
|
286
|
+
verdict: "allow",
|
|
287
|
+
determiningRules: [0],
|
|
288
|
+
errors: [],
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("every verdict DENY at exit 0 is a result, not a failure", () => {
|
|
293
|
+
// The sharpest edge in the replay contract: a nonzero exit means the trace
|
|
294
|
+
// or the policy set failed to load, never that a policy denied.
|
|
295
|
+
const allDeny = { verdicts: RECORDED.verdicts.map((v) => ({ ...v, verdict: "deny" })) };
|
|
296
|
+
const result = parseReplayOutput(json(allDeny));
|
|
297
|
+
expect(result.kind).toBe("replayed");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("an unrecognised verdict reads as a deny rather than being dropped", () => {
|
|
301
|
+
// Dropping it would shift every later index; treating it as a permit would
|
|
302
|
+
// be a permission upstream never granted.
|
|
303
|
+
const result = parseReplayOutput(json({ verdicts: [{ index: 0, timestamp: 0, verdict: "maybe" }] }));
|
|
304
|
+
expect(result.kind).toBe("replayed");
|
|
305
|
+
if (result.kind !== "replayed") return;
|
|
306
|
+
expect(result.verdicts[0].verdict).toBe("deny");
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("a malformed trace line comes back through the fatal channel", () => {
|
|
310
|
+
const result = parseReplayOutput(
|
|
311
|
+
json({ severity: "error", message: "trace parse: line 1: timepoint must start with `@`", spanned: false }, 2),
|
|
312
|
+
);
|
|
313
|
+
expect(result.kind).toBe("fatal");
|
|
314
|
+
if (result.kind !== "fatal") return;
|
|
315
|
+
expect(result.error.message).toMatch(/timepoint must start with/);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("no JSON at all is unusable, never a verdict", () => {
|
|
319
|
+
const result = parseReplayOutput(run({ status: 2, stderr: "error: unexpected argument '--nope'" }));
|
|
320
|
+
expect(result.kind).toBe("unusable");
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("per-evaluation errors are carried through in either shape", () => {
|
|
324
|
+
const result = parseReplayOutput(
|
|
325
|
+
json({
|
|
326
|
+
verdicts: [
|
|
327
|
+
{ index: 0, timestamp: 0, verdict: "deny", determining_rules: [], errors: ["no script"] },
|
|
328
|
+
{
|
|
329
|
+
index: 1,
|
|
330
|
+
timestamp: 1,
|
|
331
|
+
verdict: "deny",
|
|
332
|
+
determining_rules: [],
|
|
333
|
+
errors: [{ message: "provider failed at line 1 column 4" }],
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
}),
|
|
337
|
+
);
|
|
338
|
+
expect(result.kind).toBe("replayed");
|
|
339
|
+
if (result.kind !== "replayed") return;
|
|
340
|
+
expect(result.verdicts[0].errors).toEqual(["no script"]);
|
|
341
|
+
// The internal position is scrubbed for the same reason as everywhere else.
|
|
342
|
+
expect(result.verdicts[1].errors).toEqual(["provider failed"]);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
describe("runDogwoodReplay", () => {
|
|
347
|
+
test("passes the trace as --trace beside the bundle, and asks for JSON", () => {
|
|
348
|
+
let seen: { args: string[]; trace: string } | undefined;
|
|
349
|
+
const runner: DogwoodRunner = (_binary, args) => {
|
|
350
|
+
const at = args.indexOf("--trace");
|
|
351
|
+
seen = { args, trace: readFileSync(args[at + 1], "utf-8") };
|
|
352
|
+
return run({ status: 0, stdout: JSON.stringify({ verdicts: [] }) });
|
|
353
|
+
};
|
|
354
|
+
configureDogwoodCli({ runner });
|
|
355
|
+
|
|
356
|
+
const result = runDogwoodReplay("/bin/dogwood", { policies: "p", policySchema: "s" }, "@0 A::Action::\"B\"::request()");
|
|
357
|
+
expect(result.kind).toBe("replayed");
|
|
358
|
+
expect(seen?.args[0]).toBe("replay");
|
|
359
|
+
expect(seen?.args.slice(-2)).toEqual(["--format", "json"]);
|
|
360
|
+
expect(seen?.trace).toBe('@0 A::Action::"B"::request()');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("the scratch directory, trace included, does not outlive the run", () => {
|
|
364
|
+
let tracePath = "";
|
|
365
|
+
const runner: DogwoodRunner = (_binary, args) => {
|
|
366
|
+
tracePath = args[args.indexOf("--trace") + 1];
|
|
367
|
+
return run({ status: 0, stdout: JSON.stringify({ verdicts: [] }) });
|
|
368
|
+
};
|
|
369
|
+
configureDogwoodCli({ runner });
|
|
370
|
+
runDogwoodReplay("/bin/dogwood", { policies: "p", policySchema: "s" }, "@0 A::Action::\"B\"::request()");
|
|
371
|
+
expect(existsSync(tracePath)).toBe(false);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("a throwing runner is an unusable run, not an exception", () => {
|
|
375
|
+
configureDogwoodCli({
|
|
376
|
+
runner: () => {
|
|
377
|
+
throw new Error("spawn blew up");
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
const result = runDogwoodReplay("/bin/dogwood", { policies: "p", policySchema: "s" }, "@0 x");
|
|
381
|
+
expect(result.kind).toBe("unusable");
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
264
385
|
// ── Invocation ─────────────────────────────────────────────────────
|
|
265
386
|
|
|
266
387
|
describe("the invocation itself", () => {
|
package/src/dogwood/cli.ts
CHANGED
|
@@ -347,6 +347,29 @@ export interface DogwoodLowered {
|
|
|
347
347
|
|
|
348
348
|
export type DogwoodLowerResult = { kind: "lowered"; value: DogwoodLowered } | DogwoodFatal | DogwoodUnusable;
|
|
349
349
|
|
|
350
|
+
/**
|
|
351
|
+
* One decision point out of `replay --format json`.
|
|
352
|
+
*
|
|
353
|
+
* `index` is the 0-based position in the *decision* stream, not the trace line
|
|
354
|
+
* number: a history-only event (any kind the event schema does not mark
|
|
355
|
+
* `decision`) contributes history and no verdict, so the two do not line up.
|
|
356
|
+
*/
|
|
357
|
+
export interface DogwoodVerdict {
|
|
358
|
+
index: number;
|
|
359
|
+
/** The `@<timestamp>` of the line that produced this decision. */
|
|
360
|
+
timestamp: number;
|
|
361
|
+
verdict: "allow" | "deny";
|
|
362
|
+
/** `.dw` rule indices that determined the decision — upstream's `[rules: 0, 2]`. */
|
|
363
|
+
determiningRules: number[];
|
|
364
|
+
/** Per-evaluation errors, such as a provider with no inlined Rhai script. */
|
|
365
|
+
errors: string[];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export type DogwoodReplayResult =
|
|
369
|
+
| { kind: "replayed"; verdicts: DogwoodVerdict[] }
|
|
370
|
+
| DogwoodFatal
|
|
371
|
+
| DogwoodUnusable;
|
|
372
|
+
|
|
350
373
|
function unusable(reason: string): DogwoodUnusable {
|
|
351
374
|
return { kind: "unusable", reason };
|
|
352
375
|
}
|
|
@@ -430,6 +453,69 @@ export function parseLowerOutput(run: DogwoodRun): DogwoodLowerResult {
|
|
|
430
453
|
return unusable("`dogwood lower --format json` returned JSON in neither the artifact nor the error shape");
|
|
431
454
|
}
|
|
432
455
|
|
|
456
|
+
/**
|
|
457
|
+
* Read a `replay --format json` run.
|
|
458
|
+
*
|
|
459
|
+
* The one place the exit-code discipline matters most: **replay exits 0 even
|
|
460
|
+
* when every verdict is DENY.** A nonzero exit means the trace or the policy
|
|
461
|
+
* set failed to load, never that a policy denied — so a caller that read the
|
|
462
|
+
* exit code as a verdict would report a working deny-by-default policy set as
|
|
463
|
+
* a broken one. As everywhere else in this module, the JSON decides: a body
|
|
464
|
+
* carrying `verdicts` is the report, a body carrying `message` is the fatal
|
|
465
|
+
* `OpError` (a malformed trace line lands here), anything else is unusable.
|
|
466
|
+
*/
|
|
467
|
+
export function parseReplayOutput(run: DogwoodRun): DogwoodReplayResult {
|
|
468
|
+
if (run.error) return unusable(`the dogwood binary could not be run: ${run.error}`);
|
|
469
|
+
|
|
470
|
+
const parsed = readJson(run.stdout);
|
|
471
|
+
if (!parsed) {
|
|
472
|
+
return unusable(
|
|
473
|
+
`\`dogwood replay --format json\` exited ${String(run.status)} without JSON on stdout${streamNote(run)}`,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (Array.isArray(parsed.verdicts)) {
|
|
478
|
+
return { kind: "replayed", verdicts: parseVerdicts(parsed.verdicts) };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const fatal = fatalFrom(parsed);
|
|
482
|
+
if (fatal) return fatal;
|
|
483
|
+
|
|
484
|
+
return unusable("`dogwood replay --format json` returned JSON in neither the verdict nor the error shape");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function parseVerdicts(value: unknown[]): DogwoodVerdict[] {
|
|
488
|
+
const out: DogwoodVerdict[] = [];
|
|
489
|
+
value.forEach((raw, position) => {
|
|
490
|
+
if (!isRecord(raw)) return;
|
|
491
|
+
// `allow`/`deny` lowercase, as upstream serializes the enum. Anything else
|
|
492
|
+
// is read as a deny rather than dropped: an unrecognised verdict is not a
|
|
493
|
+
// permission, and dropping the entry would shift every later index.
|
|
494
|
+
const verdict = raw.verdict === "allow" ? "allow" : "deny";
|
|
495
|
+
out.push({
|
|
496
|
+
index: typeof raw.index === "number" ? raw.index : position,
|
|
497
|
+
timestamp: typeof raw.timestamp === "number" ? raw.timestamp : 0,
|
|
498
|
+
verdict,
|
|
499
|
+
determiningRules: Array.isArray(raw.determining_rules)
|
|
500
|
+
? raw.determining_rules.filter((r): r is number => typeof r === "number")
|
|
501
|
+
: [],
|
|
502
|
+
errors: errorList(raw.errors),
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
return out;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** Per-evaluation errors, tolerating both a string list and a diagnostic list. */
|
|
509
|
+
function errorList(value: unknown): string[] {
|
|
510
|
+
if (!Array.isArray(value)) return [];
|
|
511
|
+
const out: string[] = [];
|
|
512
|
+
for (const entry of value) {
|
|
513
|
+
if (typeof entry === "string") out.push(entry);
|
|
514
|
+
else if (isRecord(entry) && typeof entry.message === "string") out.push(scrubPosition(entry.message));
|
|
515
|
+
}
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
|
|
433
519
|
function stringList(value: unknown): string[] {
|
|
434
520
|
return Array.isArray(value) ? value.filter((v): v is string => typeof v === "string") : [];
|
|
435
521
|
}
|
|
@@ -481,6 +567,13 @@ const defaultRunner: DogwoodRunner = (binary, args) => {
|
|
|
481
567
|
};
|
|
482
568
|
};
|
|
483
569
|
|
|
570
|
+
/** An extra file+flag pair a subcommand needs — `replay`'s `--trace`, today. */
|
|
571
|
+
interface ExtraInput {
|
|
572
|
+
flag: string;
|
|
573
|
+
filename: string;
|
|
574
|
+
content: string;
|
|
575
|
+
}
|
|
576
|
+
|
|
484
577
|
/**
|
|
485
578
|
* Materialize a bundle into a scratch directory and run one subcommand.
|
|
486
579
|
*
|
|
@@ -488,7 +581,12 @@ const defaultRunner: DogwoodRunner = (binary, args) => {
|
|
|
488
581
|
* schema flags all take paths. The directory is removed in a `finally`, so a
|
|
489
582
|
* throwing runner does not leave one behind.
|
|
490
583
|
*/
|
|
491
|
-
function runBundle(
|
|
584
|
+
function runBundle(
|
|
585
|
+
binary: string,
|
|
586
|
+
subcommand: string,
|
|
587
|
+
bundle: DogwoodBundle,
|
|
588
|
+
extras: ExtraInput[] = [],
|
|
589
|
+
): DogwoodRun {
|
|
492
590
|
const runner = defaults.runner ?? defaultRunner;
|
|
493
591
|
const dir = mkdtempSync(join(tmpdir(), "chant-dogwood-"));
|
|
494
592
|
try {
|
|
@@ -515,6 +613,12 @@ function runBundle(binary: string, subcommand: string, bundle: DogwoodBundle): D
|
|
|
515
613
|
args.push("--providers", path);
|
|
516
614
|
}
|
|
517
615
|
|
|
616
|
+
for (const extra of extras) {
|
|
617
|
+
const path = join(dir, extra.filename);
|
|
618
|
+
writeFileSync(path, extra.content, "utf-8");
|
|
619
|
+
args.push(extra.flag, path);
|
|
620
|
+
}
|
|
621
|
+
|
|
518
622
|
// No `--emit`: it is ignored under `--format json`, which returns all three
|
|
519
623
|
// lowered artifacts regardless. Passing it would imply a choice that the
|
|
520
624
|
// CLI does not honour.
|
|
@@ -543,3 +647,20 @@ export function runDogwoodLower(binary: string, bundle: DogwoodBundle): DogwoodL
|
|
|
543
647
|
return unusable(`the dogwood lower run failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
544
648
|
}
|
|
545
649
|
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Run `dogwood replay` over a bundle and a trace. Never throws.
|
|
653
|
+
*
|
|
654
|
+
* The trace is materialized beside the bundle's own files and passed as
|
|
655
|
+
* `--trace`; the same scratch directory is removed in the same `finally`, so a
|
|
656
|
+
* trace carrying a session's request payloads does not outlive the run.
|
|
657
|
+
*/
|
|
658
|
+
export function runDogwoodReplay(binary: string, bundle: DogwoodBundle, trace: string): DogwoodReplayResult {
|
|
659
|
+
try {
|
|
660
|
+
return parseReplayOutput(
|
|
661
|
+
runBundle(binary, "replay", bundle, [{ flag: "--trace", filename: "trace.log", content: trace }]),
|
|
662
|
+
);
|
|
663
|
+
} catch (err) {
|
|
664
|
+
return unusable(`the dogwood replay run failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
package/src/dogwood/index.ts
CHANGED
|
@@ -149,10 +149,11 @@ export type { EventSchemaProps, MacroLibraryProps, TemporalPolicyProps } from ".
|
|
|
149
149
|
export {
|
|
150
150
|
DOGWOOD_EVENT_SCHEMA_SUFFIX,
|
|
151
151
|
DOGWOOD_POLICY_SUFFIX,
|
|
152
|
+
dogwoodPolicyRecords,
|
|
152
153
|
renderTemporalPolicyText,
|
|
153
154
|
serializeDogwood,
|
|
154
155
|
} from "./serialize";
|
|
155
|
-
export type { DogwoodSerializeResult } from "./serialize";
|
|
156
|
+
export type { DogwoodPolicyRecord, DogwoodSerializeResult } from "./serialize";
|
|
156
157
|
|
|
157
158
|
export {
|
|
158
159
|
blankComments,
|
|
@@ -177,9 +178,11 @@ export {
|
|
|
177
178
|
findDogwoodBinary,
|
|
178
179
|
formatDogwoodDiagnostic,
|
|
179
180
|
parseLowerOutput,
|
|
181
|
+
parseReplayOutput,
|
|
180
182
|
parseValidateOutput,
|
|
181
183
|
resetDogwoodCli,
|
|
182
184
|
runDogwoodLower,
|
|
185
|
+
runDogwoodReplay,
|
|
183
186
|
runDogwoodValidate,
|
|
184
187
|
} from "./cli";
|
|
185
188
|
export type {
|
|
@@ -191,8 +194,78 @@ export type {
|
|
|
191
194
|
DogwoodLabel,
|
|
192
195
|
DogwoodLowerResult,
|
|
193
196
|
DogwoodLowered,
|
|
197
|
+
DogwoodReplayResult,
|
|
194
198
|
DogwoodRun,
|
|
195
199
|
DogwoodRunner,
|
|
196
200
|
DogwoodUnusable,
|
|
197
201
|
DogwoodValidateResult,
|
|
202
|
+
DogwoodVerdict,
|
|
198
203
|
} from "./cli";
|
|
204
|
+
|
|
205
|
+
// Replay traces (#1661) — typed construction of the §6 line grammar, with
|
|
206
|
+
// both-bag population as the default and the weakening as an opt-out.
|
|
207
|
+
export {
|
|
208
|
+
auditTrace,
|
|
209
|
+
decimalValue,
|
|
210
|
+
entityRef,
|
|
211
|
+
rawValue,
|
|
212
|
+
renderTrace,
|
|
213
|
+
renderTraceFields,
|
|
214
|
+
renderTraceLine,
|
|
215
|
+
renderTraceValue,
|
|
216
|
+
traceEntity,
|
|
217
|
+
traceEvent,
|
|
218
|
+
traceFixture,
|
|
219
|
+
} from "./trace";
|
|
220
|
+
export type {
|
|
221
|
+
TraceAuditOptions,
|
|
222
|
+
TraceBags,
|
|
223
|
+
TraceDecimal,
|
|
224
|
+
TraceEntityDecl,
|
|
225
|
+
TraceEntityRef,
|
|
226
|
+
TraceEvent,
|
|
227
|
+
TraceEventInput,
|
|
228
|
+
TraceFields,
|
|
229
|
+
TraceFixture,
|
|
230
|
+
TraceFixtureOptions,
|
|
231
|
+
TraceIssue,
|
|
232
|
+
TraceIssueKind,
|
|
233
|
+
TraceRaw,
|
|
234
|
+
TraceScope,
|
|
235
|
+
TraceTagged,
|
|
236
|
+
TraceValue,
|
|
237
|
+
} from "./trace";
|
|
238
|
+
|
|
239
|
+
// The replay activities (#1661). Contributed by name through
|
|
240
|
+
// `src/op/activities/index.ts`; the helpers are here for direct use.
|
|
241
|
+
export {
|
|
242
|
+
compareVerdicts,
|
|
243
|
+
dogwoodReplay,
|
|
244
|
+
dogwoodReplayReport,
|
|
245
|
+
renderReplaySummary,
|
|
246
|
+
resolveReplayInputs,
|
|
247
|
+
} from "./replay-activity";
|
|
248
|
+
export type {
|
|
249
|
+
DogwoodReplayArgs,
|
|
250
|
+
DogwoodReplayReportArgs,
|
|
251
|
+
ExpectedVerdict,
|
|
252
|
+
PolicyReplayDispatch,
|
|
253
|
+
PolicyReplayMode,
|
|
254
|
+
PolicyReplayReport,
|
|
255
|
+
ReplayDivergence,
|
|
256
|
+
ReplayExpectation,
|
|
257
|
+
} from "./replay-activity";
|
|
258
|
+
|
|
259
|
+
// The PolicyReplayOp composite and its typed step builders (#1661).
|
|
260
|
+
export {
|
|
261
|
+
DEFAULT_REPLAY_REPORT_PATH,
|
|
262
|
+
PolicyReplayOp,
|
|
263
|
+
dogwoodReplayReportStep,
|
|
264
|
+
dogwoodReplayStep,
|
|
265
|
+
} from "./replay-op";
|
|
266
|
+
export type {
|
|
267
|
+
DogwoodReplayReportStepOpts,
|
|
268
|
+
DogwoodReplayStepOpts,
|
|
269
|
+
PolicyReplayOpConfig,
|
|
270
|
+
PolicyReplayOpResources,
|
|
271
|
+
} from "./replay-op";
|