agent-contracts 0.11.8 → 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
@@ -998,7 +998,7 @@ Each `context` type provides a different rendering scope:
998
998
 
999
999
  | Context | Scope | Output | Key variables |
1000
1000
  |---------|-------|--------|---------------|
1001
- | `system` | Single file | `output` as-is | `system`, `dsl` |
1001
+ | `system` | Single file | `output` as-is | `system`, `dsl`, `guardrailEnforcement`\*, `bindings`\* |
1002
1002
  | `agent` | Per agent | `{agent.id}` in output path | `agent`, `receivableTasks`, `delegatableTasks`, `relatedArtifacts`, `relatedTools`, `relatedHandoffTypes`, `mergedBehavior`, `dsl` |
1003
1003
  | `task` | Per task | `{task.id}` in output path | `task`, `targetAgent`, `dsl` |
1004
1004
  | `artifact` | Per artifact | `{artifact.id}` in output path | `artifact`, `relatedTools`, `relatedValidations`, `producerAgents`, `consumerAgents`, `editorAgents`, `createdInWorkflows`, `dsl` |
@@ -1027,6 +1027,18 @@ Each `context` type provides a different rendering scope:
1027
1027
  * `producerAgents` / `consumerAgents` / `editorAgents` — resolved agent records
1028
1028
  * `createdInWorkflows` — workflow phases where this artifact is written
1029
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
+
1030
1042
  **`tool` context** resolves agent and artifact references:
1031
1043
 
1032
1044
  * `invokableAgents` — agents listed in `invokable_by`
package/dist/cli.js CHANGED
@@ -53574,8 +53574,52 @@ import { dirname as dirname5 } from "path";
53574
53574
  import Handlebars from "handlebars";
53575
53575
 
53576
53576
  // src/renderer/context.ts
53577
- function buildSystemContext(dsl) {
53578
- 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;
53579
53623
  }
53580
53624
  function buildTaskContext(dsl, taskId) {
53581
53625
  const taskDef = dsl.tasks[taskId];
@@ -54630,6 +54674,50 @@ Handlebars.registerHelper(
54630
54674
  return generateOverviewFlowchart(dsl);
54631
54675
  }
54632
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
+ );
54633
54721
  function getDslSection(dsl, context) {
54634
54722
  const sectionMap = {
54635
54723
  agent: dsl.agents,
@@ -54694,13 +54782,13 @@ async function removeIfExists(filePath) {
54694
54782
  } catch {
54695
54783
  }
54696
54784
  }
54697
- async function renderFromConfig(dsl, renderTargets) {
54785
+ async function renderFromConfig(dsl, renderTargets, options) {
54698
54786
  const outputFiles = [];
54699
54787
  for (const target of renderTargets) {
54700
54788
  const templateContent = await loadTemplate(target.template);
54701
54789
  const compiled = Handlebars.compile(templateContent, { noEscape: false });
54702
54790
  if (target.context === "system") {
54703
- const ctx = buildSystemContext(dsl);
54791
+ const ctx = buildSystemContext(dsl, options);
54704
54792
  const output = compiled(ctx);
54705
54793
  if (target.skip_empty && isEffectivelyEmpty(output)) {
54706
54794
  await removeIfExists(target.output);
@@ -54749,13 +54837,13 @@ function checkExpectedVsExisting(expected, outputPath, skipEmpty, diffs) {
54749
54837
  diffs.push(outputPath);
54750
54838
  });
54751
54839
  }
54752
- async function checkDriftFromConfig(dsl, renderTargets) {
54840
+ async function checkDriftFromConfig(dsl, renderTargets, options) {
54753
54841
  const diffs = [];
54754
54842
  for (const target of renderTargets) {
54755
54843
  const templateContent = await loadTemplate(target.template);
54756
54844
  const compiled = Handlebars.compile(templateContent, { noEscape: false });
54757
54845
  if (target.context === "system") {
54758
- const ctx = buildSystemContext(dsl);
54846
+ const ctx = buildSystemContext(dsl, options);
54759
54847
  const expected = compiled(ctx);
54760
54848
  await checkExpectedVsExisting(expected, target.output, target.skip_empty, diffs);
54761
54849
  } else {
@@ -54793,10 +54881,19 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
54793
54881
  );
54794
54882
  process.exit(1);
54795
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
+ }
54796
54892
  if (opts.check) {
54797
54893
  const drift = await checkDriftFromConfig(
54798
54894
  schemaResult.data,
54799
- config.renders
54895
+ config.renders,
54896
+ renderOptions
54800
54897
  );
54801
54898
  if (drift.hasDrift) {
54802
54899
  process.stderr.write("Drift detected in the following files:\n");
@@ -54812,7 +54909,8 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
54812
54909
  } else {
54813
54910
  const files = await renderFromConfig(
54814
54911
  schemaResult.data,
54815
- config.renders
54912
+ config.renders,
54913
+ renderOptions
54816
54914
  );
54817
54915
  if (!opts.quiet) {
54818
54916
  process.stdout.write(`Rendered ${files.length} file(s):