agent-contracts 0.11.7 → 0.12.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/README.md CHANGED
@@ -229,6 +229,31 @@ Workflow steps support additional properties:
229
229
  * `retry` (delegate steps) — defines a conditional retry loop with `condition`, `fix_task`, and optional `revalidate_task`
230
230
  * `routing_key` (decision steps) — the field that determines branch selection. The legacy field `on` is still accepted but deprecated due to YAML 1.1 reserved word collision
231
231
 
232
+ ### Validation
233
+
234
+ A **Validation** defines a verification step for an artifact:
235
+
236
+ * `target_artifact` — the artifact being verified
237
+ * `kind` — the type of verification (see below)
238
+ * `executor_type` — `tool` (automated) or `agent` (agent-driven)
239
+ * `executor` — the tool or agent that runs the validation
240
+ * `blocking` — whether the validation must pass before proceeding
241
+ * `produces_evidence` — optional artifact produced as evidence
242
+
243
+ #### Validation kinds
244
+
245
+ | Kind | Purpose | Example |
246
+ |------|---------|---------|
247
+ | `schema` | Structural schema check | JSON Schema validation, OpenAPI lint, SQL syntax |
248
+ | `mechanical` | Automated tool check | CLI linters, diff checks, coverage reports |
249
+ | `semantic` | Meaning-level review | Agent-based review of spec intent, plan coherence |
250
+ | `approval` | Human/agent sign-off gate | Architect approval before implementation |
251
+ | `provenance` | Source derivation verification | Confirm generated artifact derives from its canonical source (e.g., manifest from API contracts) |
252
+ | `traceability` | Cross-artifact link completeness | Verify every spec requirement reaches contracts, tests, and code |
253
+ | `fidelity` | Semantic faithfulness to source | Confirm tests actually verify spec intent, not just structural compliance |
254
+
255
+ `schema` and `mechanical` are best suited for automated checks via tools. `semantic`, `fidelity`, and `approval` are typically agent-driven. `provenance` and `traceability` can be either tool or agent-based depending on the verification complexity.
256
+
232
257
  ### Guardrail
233
258
 
234
259
  A **Guardrail** declares a cross-cutting constraint:
@@ -973,7 +998,7 @@ Each `context` type provides a different rendering scope:
973
998
 
974
999
  | Context | Scope | Output | Key variables |
975
1000
  |---------|-------|--------|---------------|
976
- | `system` | Single file | `output` as-is | `system`, `dsl` |
1001
+ | `system` | Single file | `output` as-is | `system`, `dsl`, `guardrailEnforcement`\*, `bindings`\* |
977
1002
  | `agent` | Per agent | `{agent.id}` in output path | `agent`, `receivableTasks`, `delegatableTasks`, `relatedArtifacts`, `relatedTools`, `relatedHandoffTypes`, `mergedBehavior`, `dsl` |
978
1003
  | `task` | Per task | `{task.id}` in output path | `task`, `targetAgent`, `dsl` |
979
1004
  | `artifact` | Per artifact | `{artifact.id}` in output path | `artifact`, `relatedTools`, `relatedValidations`, `producerAgents`, `consumerAgents`, `editorAgents`, `createdInWorkflows`, `dsl` |
@@ -1002,6 +1027,18 @@ Each `context` type provides a different rendering scope:
1002
1027
  * `producerAgents` / `consumerAgents` / `editorAgents` — resolved agent records
1003
1028
  * `createdInWorkflows` — workflow phases where this artifact is written
1004
1029
 
1030
+ **`system` context** includes binding-aware guardrail enforcement data when `bindings` and `active_guardrail_policy` are configured:
1031
+
1032
+ * `guardrailEnforcement` — array of enforcement entries, each with `guardrail_id`, `description`, `severity`, `action`, scoped entities (`scoped_agents`, `scoped_tasks`, `scoped_workflows`, `scoped_tools`, `scoped_artifacts`), `allow_override`, `override_requires`, `trigger` (from binding matcher type), and `escalation`
1033
+ * `bindings` — array of loaded `SoftwareBinding` objects
1034
+
1035
+ These fields are only populated when the config specifies `bindings` and `active_guardrail_policy`. Existing templates that do not reference these fields are unaffected.
1036
+
1037
+ **Matrix helpers** are available in `context: system` templates:
1038
+
1039
+ * `guardrailCoverageMatrix` — generates a Guardrail Coverage Matrix table (guardrail × severity × action × scoped entities × trigger × override × escalation)
1040
+ * `taskGuardrailMatrix` — generates a Task × Guardrail cross-reference table showing which action applies to each task
1041
+
1005
1042
  **`tool` context** resolves agent and artifact references:
1006
1043
 
1007
1044
  * `invokableAgents` — agents listed in `invokable_by`
@@ -1268,6 +1305,7 @@ Checks:
1268
1305
  * prerequisite readability
1269
1306
  * artifact ownership — `produces_artifact`/`reads_artifact` in execution steps vs. artifact producers/editors/consumers
1270
1307
  * tool commands — `commands[].reads`/`commands[].writes` reference valid artifacts and align with `output_artifacts`
1308
+ * semantic validation phase coverage — warns when `semantic` or `fidelity` validations only appear in late workflow phases (e.g., audit) but not earlier phases (e.g., specify, plan)
1271
1309
  * YAML safety — warns when YAML 1.1 reserved words (`on`, `yes`, `no`, `true`, `false`, etc.) are used in positions where they may be misinterpreted by non-1.2 parsers
1272
1310
  * naming/style issues through Spectral rules
1273
1311
 
package/dist/cli.js CHANGED
@@ -50676,7 +50676,7 @@ var ToolSchema = z9.object({
50676
50676
  import { z as z10 } from "zod";
50677
50677
  var ValidationSchema = z10.object({
50678
50678
  target_artifact: z10.string(),
50679
- kind: z10.enum(["schema", "mechanical", "semantic", "approval"]),
50679
+ kind: z10.enum(["schema", "mechanical", "semantic", "approval", "provenance", "traceability", "fidelity"]),
50680
50680
  executor_type: z10.enum(["tool", "agent"]),
50681
50681
  executor: z10.string(),
50682
50682
  blocking: z10.boolean(),
@@ -52800,6 +52800,68 @@ var taskOutputValidationCompletenessRule = {
52800
52800
  }
52801
52801
  };
52802
52802
 
52803
+ // src/linter/rules/semantic-validation-phase-coverage.ts
52804
+ var SEMANTIC_KINDS = /* @__PURE__ */ new Set(["semantic", "fidelity"]);
52805
+ var semanticValidationPhaseCoverageRule = {
52806
+ id: "semantic-validation-phase-coverage",
52807
+ description: "Semantic and fidelity validations should appear in early workflow phases, not only in late phases.",
52808
+ run(dsl) {
52809
+ const diagnostics = [];
52810
+ const semanticValIds = /* @__PURE__ */ new Set();
52811
+ for (const [valId, val] of Object.entries(dsl.validations)) {
52812
+ if (SEMANTIC_KINDS.has(val.kind)) {
52813
+ semanticValIds.add(valId);
52814
+ }
52815
+ }
52816
+ if (semanticValIds.size === 0) {
52817
+ return diagnostics;
52818
+ }
52819
+ const phaseOrder = dsl.system.default_workflow_order;
52820
+ if (phaseOrder.length < 2) {
52821
+ return diagnostics;
52822
+ }
52823
+ const phaseValidations = /* @__PURE__ */ new Map();
52824
+ for (const phase of phaseOrder) {
52825
+ phaseValidations.set(phase, /* @__PURE__ */ new Set());
52826
+ }
52827
+ for (const [phase, wf] of Object.entries(dsl.workflow)) {
52828
+ if (!phaseValidations.has(phase)) continue;
52829
+ for (const step of wf.steps) {
52830
+ if (step.type === "validation" && semanticValIds.has(step.validation)) {
52831
+ phaseValidations.get(phase).add(step.validation);
52832
+ }
52833
+ }
52834
+ }
52835
+ for (const task of Object.values(dsl.tasks)) {
52836
+ const phase = task.workflow;
52837
+ if (!phaseValidations.has(phase)) continue;
52838
+ for (const valId of task.validations) {
52839
+ if (semanticValIds.has(valId)) {
52840
+ phaseValidations.get(phase).add(valId);
52841
+ }
52842
+ }
52843
+ }
52844
+ const earlyBoundary = Math.ceil(phaseOrder.length / 2);
52845
+ const earlyPhases = phaseOrder.slice(0, earlyBoundary);
52846
+ const latePhases = phaseOrder.slice(earlyBoundary);
52847
+ const earlyHasAny = earlyPhases.some(
52848
+ (p2) => (phaseValidations.get(p2)?.size ?? 0) > 0
52849
+ );
52850
+ const lateWithSemantic = latePhases.filter(
52851
+ (p2) => (phaseValidations.get(p2)?.size ?? 0) > 0
52852
+ );
52853
+ if (!earlyHasAny && lateWithSemantic.length > 0) {
52854
+ diagnostics.push({
52855
+ ruleId: "semantic-validation-phase-coverage",
52856
+ severity: "warning",
52857
+ path: "validations",
52858
+ message: `Semantic validations are only referenced in workflow phases [${lateWithSemantic.join(", ")}]. Consider adding semantic review to earlier phases [${earlyPhases.join(", ")}] to catch issues before implementation.`
52859
+ });
52860
+ }
52861
+ return diagnostics;
52862
+ }
52863
+ };
52864
+
52803
52865
  // src/linter/linter.ts
52804
52866
  var builtinRules = [
52805
52867
  validationCoverageRule,
@@ -52811,7 +52873,8 @@ var builtinRules = [
52811
52873
  guardrailPolicyCoverageRule,
52812
52874
  yamlReservedKeySafetyRule,
52813
52875
  artifactRequiredValidationWiringRule,
52814
- taskOutputValidationCompletenessRule
52876
+ taskOutputValidationCompletenessRule,
52877
+ semanticValidationPhaseCoverageRule
52815
52878
  ];
52816
52879
  function lint(dsl, rules = builtinRules) {
52817
52880
  const diagnostics = [];
@@ -53511,8 +53574,52 @@ import { dirname as dirname5 } from "path";
53511
53574
  import Handlebars from "handlebars";
53512
53575
 
53513
53576
  // src/renderer/context.ts
53514
- function buildSystemContext(dsl) {
53515
- return { system: dsl.system, dsl };
53577
+ function buildSystemContext(dsl, options) {
53578
+ const ctx = { system: dsl.system, dsl };
53579
+ if (options?.loadedBindings && options.loadedBindings.length > 0) {
53580
+ ctx.bindings = options.loadedBindings.map((lb) => lb.binding);
53581
+ const policyName = options.activeGuardrailPolicy;
53582
+ const policy = policyName ? dsl.guardrail_policies[policyName] : void 0;
53583
+ if (policy) {
53584
+ ctx.guardrailEnforcement = buildGuardrailEnforcement(dsl, policy, options.loadedBindings);
53585
+ }
53586
+ }
53587
+ return ctx;
53588
+ }
53589
+ function buildGuardrailEnforcement(dsl, policy, loadedBindings) {
53590
+ const entries = [];
53591
+ const bindingTriggers = /* @__PURE__ */ new Map();
53592
+ for (const lb of loadedBindings) {
53593
+ const impl = lb.binding.guardrail_impl ?? {};
53594
+ for (const [guardrailId, gi] of Object.entries(impl)) {
53595
+ for (const check of gi.checks) {
53596
+ if (check.matcher) {
53597
+ bindingTriggers.set(guardrailId, check.matcher.type);
53598
+ }
53599
+ }
53600
+ }
53601
+ }
53602
+ for (const rule of policy.rules) {
53603
+ const guardrail = dsl.guardrails[rule.guardrail];
53604
+ if (!guardrail) continue;
53605
+ const scope = guardrail.scope ?? {};
53606
+ entries.push({
53607
+ guardrail_id: rule.guardrail,
53608
+ description: guardrail.description,
53609
+ severity: rule.severity,
53610
+ action: rule.action,
53611
+ scoped_agents: scope.agents ?? [],
53612
+ scoped_tasks: scope.tasks ?? [],
53613
+ scoped_workflows: scope.workflows ?? [],
53614
+ scoped_tools: scope.tools ?? [],
53615
+ scoped_artifacts: scope.artifacts ?? [],
53616
+ allow_override: rule.allow_override,
53617
+ override_requires: rule.override_requires ?? [],
53618
+ trigger: bindingTriggers.get(rule.guardrail) ?? null,
53619
+ escalation: rule.escalation ? { target: rule.escalation.target, condition: rule.escalation.condition } : null
53620
+ });
53621
+ }
53622
+ return entries;
53516
53623
  }
53517
53624
  function buildTaskContext(dsl, taskId) {
53518
53625
  const taskDef = dsl.tasks[taskId];
@@ -54567,6 +54674,50 @@ Handlebars.registerHelper(
54567
54674
  return generateOverviewFlowchart(dsl);
54568
54675
  }
54569
54676
  );
54677
+ Handlebars.registerHelper(
54678
+ "guardrailCoverageMatrix",
54679
+ function() {
54680
+ const enforcement = this["guardrailEnforcement"];
54681
+ if (!enforcement || enforcement.length === 0) return "";
54682
+ const header = "| Guardrail | Severity | Action | Agents | Tasks | Workflows | Tools | Artifacts | Trigger | Override | Escalation |";
54683
+ const divider = "|-----------|----------|--------|--------|-------|-----------|-------|-----------|---------|----------|------------|";
54684
+ const rows = enforcement.map((e3) => {
54685
+ const agents = e3["scoped_agents"]?.join(", ") || "\u2014";
54686
+ const tasks = e3["scoped_tasks"]?.join(", ") || "\u2014";
54687
+ const workflows = e3["scoped_workflows"]?.join(", ") || "\u2014";
54688
+ const tools = e3["scoped_tools"]?.join(", ") || "\u2014";
54689
+ const artifacts = e3["scoped_artifacts"]?.join(", ") || "\u2014";
54690
+ const trigger = e3["trigger"] || "\u2014";
54691
+ const override = e3["allow_override"] ? "yes" : "no";
54692
+ const esc = e3["escalation"];
54693
+ const escalation = esc ? esc["target"] : "\u2014";
54694
+ return `| ${e3["guardrail_id"]} | ${e3["severity"]} | ${e3["action"]} | ${agents} | ${tasks} | ${workflows} | ${tools} | ${artifacts} | ${trigger} | ${override} | ${escalation} |`;
54695
+ });
54696
+ return [header, divider, ...rows].join("\n");
54697
+ }
54698
+ );
54699
+ Handlebars.registerHelper(
54700
+ "taskGuardrailMatrix",
54701
+ function() {
54702
+ const dsl = this["dsl"];
54703
+ const enforcement = this["guardrailEnforcement"];
54704
+ if (!dsl || !enforcement || enforcement.length === 0) return "";
54705
+ const taskIds = Object.keys(dsl.tasks);
54706
+ if (taskIds.length === 0) return "";
54707
+ const guardrailIds = enforcement.map((e3) => e3["guardrail_id"]);
54708
+ const header = `| Task | ${guardrailIds.join(" | ")} |`;
54709
+ const divider = `|------|${guardrailIds.map(() => "------").join("|")}|`;
54710
+ const rows = taskIds.map((taskId) => {
54711
+ const cells = enforcement.map((e3) => {
54712
+ const scopedTasks = e3["scoped_tasks"];
54713
+ if (scopedTasks.length > 0 && !scopedTasks.includes(taskId)) return "n/a";
54714
+ return e3["action"];
54715
+ });
54716
+ return `| ${taskId} | ${cells.join(" | ")} |`;
54717
+ });
54718
+ return [header, divider, ...rows].join("\n");
54719
+ }
54720
+ );
54570
54721
  function getDslSection(dsl, context) {
54571
54722
  const sectionMap = {
54572
54723
  agent: dsl.agents,
@@ -54631,13 +54782,13 @@ async function removeIfExists(filePath) {
54631
54782
  } catch {
54632
54783
  }
54633
54784
  }
54634
- async function renderFromConfig(dsl, renderTargets) {
54785
+ async function renderFromConfig(dsl, renderTargets, options) {
54635
54786
  const outputFiles = [];
54636
54787
  for (const target of renderTargets) {
54637
54788
  const templateContent = await loadTemplate(target.template);
54638
54789
  const compiled = Handlebars.compile(templateContent, { noEscape: false });
54639
54790
  if (target.context === "system") {
54640
- const ctx = buildSystemContext(dsl);
54791
+ const ctx = buildSystemContext(dsl, options);
54641
54792
  const output = compiled(ctx);
54642
54793
  if (target.skip_empty && isEffectivelyEmpty(output)) {
54643
54794
  await removeIfExists(target.output);
@@ -54686,13 +54837,13 @@ function checkExpectedVsExisting(expected, outputPath, skipEmpty, diffs) {
54686
54837
  diffs.push(outputPath);
54687
54838
  });
54688
54839
  }
54689
- async function checkDriftFromConfig(dsl, renderTargets) {
54840
+ async function checkDriftFromConfig(dsl, renderTargets, options) {
54690
54841
  const diffs = [];
54691
54842
  for (const target of renderTargets) {
54692
54843
  const templateContent = await loadTemplate(target.template);
54693
54844
  const compiled = Handlebars.compile(templateContent, { noEscape: false });
54694
54845
  if (target.context === "system") {
54695
- const ctx = buildSystemContext(dsl);
54846
+ const ctx = buildSystemContext(dsl, options);
54696
54847
  const expected = compiled(ctx);
54697
54848
  await checkExpectedVsExisting(expected, target.output, target.skip_empty, diffs);
54698
54849
  } else {
@@ -54730,10 +54881,19 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
54730
54881
  );
54731
54882
  process.exit(1);
54732
54883
  }
54884
+ let renderOptions;
54885
+ if (config.bindings.length > 0) {
54886
+ const loadedBindings = await loadBindings(config.bindings);
54887
+ renderOptions = {
54888
+ loadedBindings,
54889
+ activeGuardrailPolicy: config.activeGuardrailPolicy
54890
+ };
54891
+ }
54733
54892
  if (opts.check) {
54734
54893
  const drift = await checkDriftFromConfig(
54735
54894
  schemaResult.data,
54736
- config.renders
54895
+ config.renders,
54896
+ renderOptions
54737
54897
  );
54738
54898
  if (drift.hasDrift) {
54739
54899
  process.stderr.write("Drift detected in the following files:\n");
@@ -54749,7 +54909,8 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
54749
54909
  } else {
54750
54910
  const files = await renderFromConfig(
54751
54911
  schemaResult.data,
54752
- config.renders
54912
+ config.renders,
54913
+ renderOptions
54753
54914
  );
54754
54915
  if (!opts.quiet) {
54755
54916
  process.stdout.write(`Rendered ${files.length} file(s):