Skip to content

feat: decision architecture (invariants, packs, receipts, approvals) - #206

Merged
shreyanshjain7174 merged 54 commits into
mainfrom
feat/decision-arch
Oct 2, 2026
Merged

shreyanshjain7174 merged 54 commits into
mainfrom
feat/decision-arch

Conversation

@shreyanshjain7174

Copy link
Copy Markdown
Collaborator

What does this PR do?

Implements the decision architecture so the docs match the code. An AgentWorkload action now passes through ordered layers, and every decision ends in a signed receipt.

Layers, in order: invariants (INV-01 to INV-06), policy packs, threshold, optional model, human approval. The first deny wins. Nothing can be loosened by a later layer.

Packages

  • pkg/invariants: hard rules that no pack, model or human can override.
  • pkg/rules: policy engine (OPA, Go 1.25 compatible), packs, threshold rules.
  • pkg/dataclass, pkg/decision: data classification, the layered decision flow, model shadow mode.
  • pkg/receipts, cmd/receipt-writer: hash-chained, Ed25519-signed receipts. Uses the public pkg/receiptspec from agentgate v0.1.4.
  • pkg/approval, pkg/dataset: human approve, reject and edit via annotation protocol and webhook stamping. Exports labeled examples.
  • pkg/tracing/decisiontrace: one trace span per layer.
  • tools/decision-train: offline trainer for the model layer.
  • Chart: global.receipts and decision-model values.
  • scripts/demo-claims.sh: runs every claim end to end on kind.

Docs

docs/architecture/decision-architecture.md (ADR), invariants.md, decision-model.md, docs/policy-input.md, policy-packs.md, receipts.md, approvals.md, TRACING.md, BLOCKERS.md, CHANGELOG.md.

Type of change

  • New feature
  • Documentation update

Checklist

  • go build ./... passes
  • go vet ./... passes
  • go test ./... passes except TestControllers in internal/controller. It needs envtest assets that this machine lacks and fails the same way on main.
  • make lint passes
  • OSS/private boundary guard and issue-first guard pass locally
  • No private-repo content leaked into OSS core
  • Commits are signed off (git commit -s), human-authored
  • Documentation updated

How to test

go build ./... && go vet ./... && make lint
go test ./pkg/... ./cmd/... ./tools/...
scripts/demo-claims.sh   # needs kind, kubectl, helm, docker

demo-claims.sh prints a PASS line per claim: allow, deny by invariant, deny by pack, deny by threshold, require approval, model escalate, human approve, reject and edit.

Known gaps

Listed in BLOCKERS.md. They are documented, not hidden:

  • F7: dataset content is not bound to the original decision record.
  • F11: writer recovery trusts local files.
  • F12: deleted approval rows are not detected without a signed dataset manifest.
  • Runtime-adapter path runs no invariants, packs, receipts or approvals. Packs fail closed there.
  • Execution outcomes are not receipted. Only the pre-execution decision is.

Related issues

Relates to the decision architecture planning in agentgate. Companion docs PR: Clawdlinux/agentgate#35.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Split decision inputs by trust: declared, observed, agent-claimed.
Add pkg/dataclass deterministic detectors (email, phone, aadhaar,
pan, iban, card). Add pkg/decision/input, pkg/decision/observe, and
decision.Combine so claimed values can only tighten.

Add optional spec.declaredIntent to AgentWorkload. On the legacy
action path, undeclared data classes or destinations deny in strict
mode and escalate otherwise. Behaviour without declaredIntent is
unchanged.

Docs: docs/policy-input.md, sample manifest, ADR and README links.
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Invariants INV-01..05 in pkg/invariants. INV-01 denies credential-shaped
values even without declaredIntent. New dataclass class: credential.

pkg/rules/engine embeds OPA v1.19.1 (last release on Go 1.25). Packs
dpdp-in@v0.1.0 and gdpr-eu@v0.1.0 ship via go:embed with Rego unit tests
run under go test. Packs only add deny or require_approval.

pkg/decision.Decide merges layers, strictest wins. Pack approval never
auto-allows. Opt in with spec.policyPacks. Unknown packs are rejected by
the webhook and fail closed in the controller. Packs on orchestrated
runtimes fail closed; invariants and packs run on the direct path only.

pkg/opa moved to pkg/rules/threshold. pkg/opa kept as deprecated aliases.
agentctl migrated to tablewriter v1, which OPA requires.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…1.4 is tagged

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…pt-writer service

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…ntctl export/verify, chart

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…oval dataset

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…y ApplyModel

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
… trainer, DRAFT synthetic seed

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
… final outcome, model layer

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…webhook check

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…ct path, DECISION_MODEL_PATH

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…ith baseline comparison

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…DEL_PATH

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
…onest status

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Status-only updates re-triggered Reconcile, which re-proposed and re-ran
the direct-path action in a tight loop (1638 receipts in one demo run).
Filter on generation, annotation, label, and deletion changes. Terminal
paths already return without requeue, so nothing relied on the echo.

MCP status, proposal, and execution values can hold personal data or
credentials. Log field counts only.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
scripts/demo-claims.sh proves the decision-architecture claims with no
API keys. A deterministic mock MCP server plays the agent. Eight
scenarios print the exact command and a PASS or FAIL line, and the
script exits nonzero on any failure.

The repo Dockerfile cannot build while go.mod carries the local
receiptspec replace, so the demo builds linux binaries on the host and
packages them in a demo-only image loaded with kind load.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
kindnet enforces NetworkPolicy. The chart's default-deny egress has no
rule for an MCP endpoint, so after the scenario 7 helm upgrade the new
operator pod could not reach the mock. Allow the kind pod CIDR on the
mock port only. Pass the kind context to every helm and kubectl call.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Document scripts/demo-claims.sh with its measured runtime, what it
proves, and what it does not. Update the audit row to describe opt-in
decision receipts on the direct path.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
ADR Today vs target now lists the event filter, log redaction, the claims
demo, and what is not built. BLOCKERS gains the demo image detour and the
known gaps the demo exposed. CHANGELOG Unreleased summarizes the series.
values.yaml notes that the egress policy has no MCP endpoint rule.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Scans past depth, node, byte, or per-string limits used to drop the
rest silently. A credential past 64 KiB escaped INV-01, INV-03/04 and
packs. Scan now reports completeness and INV-06 denies incomplete
scans.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 2, 2026 04:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Key validation, cross-platform locking, feature parity, and log privacy issues remain.

Review effort: Balanced
Findings: 2 High severity · 2 Medium severity · 4 Low severity

Open (8)
What changed in this PR

This PR adds layered action decisions, signed evidence, approvals, model scoring, tracing, deployment wiring, and operator tooling.

Changes:

  • Adds invariants, Rego packs, thresholds, model escalation, approvals, and receipts.
  • Adds dataset tooling, tracing, model training, CLI commands, and tests.
  • Adds Helm resources and architecture documentation.
File Description
tools/​decision-train/​seed.py Generates synthetic training rows
tools/​decision-train/​run.sh Verifies deterministic training
tools/​decision-train/​README.md Documents trainer limits
tools/​decision-train/​out/​model.json Adds synthetic model artifact
scripts/​review_code.sh Updates threshold paths
scripts/​demo/​Dockerfile.demo Adds demo image
ROADMAP.md Updates decision roadmap
pkg/​tracing/​decisiontrace/​decisiontrace.go Adds decision tracing
pkg/​tracing/​decisiontrace/​decisiontrace_test.go Tests decision spans
pkg/​rules/​threshold/​policies.rego Relocates sample policies
pkg/​rules/​threshold/​evaluator.go Renames threshold package
pkg/​rules/​threshold/​evaluator_test.go Updates threshold tests
pkg/​rules/​packs/​packs.go Embeds policy packs
pkg/​rules/​packs/​packs_test.go Tests pack loading
pkg/​rules/​packs/​gdpr-eu@v0.1.0/​README.md Documents GDPR pack
pkg/​rules/​packs/​gdpr-eu@v0.1.0/​gdpr.rego Adds GDPR rules
pkg/​rules/​packs/​gdpr-eu@v0.1.0/​gdpr_test.rego Tests GDPR rules
pkg/​rules/​packs/​dpdp-in@v0.1.0/​README.md Documents DPDP pack
pkg/​rules/​packs/​dpdp-in@v0.1.0/​dpdp.rego Adds DPDP rules
pkg/​rules/​packs/​dpdp-in@v0.1.0/​dpdp_test.rego Tests DPDP rules
pkg/​receipts/​writer.go Defines receipt writer
pkg/​receipts/​trustroot.go Builds receipt trust roots
pkg/​receipts/​model_test.go Tests model receipt blocks
pkg/​receipts/​lock_unix.go Locks Unix receipt chains
pkg/​receipts/​lock_other.go Handles non-Unix locking
pkg/​receipts/​human_test.go Tests human receipts
pkg/​opa/​opa.go Preserves deprecated API
pkg/​opa/​opa_test.go Tests compatibility API
pkg/​mcp/​client.go Redacts MCP errors
pkg/​invariants/​invariants.go Implements decision invariants
pkg/​invariants/​invariants_test.go Tests invariant ordering
pkg/​decision/​observe/​observe.go Builds observed inputs
pkg/​decision/​observe/​observe_test.go Tests input observation
pkg/​decision/​learned/​claims_test.go Tests claim safety
pkg/​decision/​input/​input.go Defines trusted input classes
pkg/​decision/​input/​input_test.go Tests input violations
pkg/​decision/​decision.go Combines decision layers
pkg/​decision/​decision_test.go Tests strictest outcomes
pkg/​decision/​baseline/​baseline.go Adds baseline scorer
pkg/​decision/​baseline/​baseline_test.go Tests baseline scoring
pkg/​dataset/​verify.go Verifies dataset bindings
pkg/​dataset/​store.go Stores approval examples
pkg/​dataset/​remote.go Adds dataset client
pkg/​dataset/​features.go Maps dataset features
pkg/​dataset/​features_test.go Tests feature mapping
pkg/​agentctl/​types.go Extends approval results
pkg/​agentctl/​reject.go Records reject decisions
pkg/​agentctl/​decide.go Adds decision annotations
pkg/​agentctl/​approve.go Records approval decisions
Makefile Adds build and training targets
internal/​controller/​workload_event_filter_test.go Tests event filtering
internal/​controller/​reservation.go Reserves execution safely
internal/​controller/​receipts.go Integrates decision receipts
internal/​controller/​mcp_log_canary_test.go Tests MCP log privacy
internal/​controller/​decision_model.go Integrates model scoring
internal/​controller/​agentworkload_controller_reconcile_integration_test.go Extends MCP scenarios
docs/​SHOWCASE-DEMO-WALKTHROUGH.md Updates OPA guidance
docs/​security/​threat-model.md Adds decision threats
docs/​security/​README.md Updates security links
docs/​security/​cross-cluster-identity.md Updates related links
docs/​policy-packs.md Documents policy packs
docs/​policy-input.md Documents decision inputs
docs/​architecture/​invariants.md Documents invariants
docs/​07-security.md Documents layered policy
docs/​04-architecture.md Links decision architecture
docs/​03-configuration.md Documents policy configuration
Dockerfile Adds receipt-writer target
config/​samples/​agentworkload_declared_intent.yaml Adds declared-intent sample
config/​policies/​README.md Clarifies sample Rego
config/​policies/​kustomization.yaml Labels sample policies
cmd/​receipt-writer/​approvals_test.go Tests approval endpoints
cmd/​main.go Wires decision services
cmd/​main_test.go Tests service configuration
cmd/​agentctl/​root.go Registers new commands
cmd/​agentctl/​decision_test.go Tests model evaluation
cmd/​agentctl/​dataset.go Adds dataset commands
cmd/​agentctl/​commands.go Adds approval workflows
charts/​values.yaml Adds decision values
charts/​values.schema.json Validates decision values
charts/​tests/​decision_model_test.yaml Tests model mounting
charts/​tests/​approval_stamp_key_test.yaml Tests approval key mounting
charts/​templates/​networkpolicy.yaml Allows receipt traffic
charts/​templates/​_helpers.tpl Adds writer labels
charts/​charts/​agentic-operator/​templates/​deployment.yaml Mounts decision configuration
CHANGELOG.md Records architecture changes
BLOCKERS.md Documents open gaps
api/​v1alpha1/​zz_generated.deepcopy.go Copies new API fields
api/​v1alpha1/​agentworkload_webhook.go Registers validation webhooks
api/​v1alpha1/​agentworkload_webhook_test.go Tests pack and model validation
api/​v1alpha1/​agentworkload_admission.go Stamps approval decisions
api/​v1alpha1/​agentworkload_admission_test.go Tests approval admission

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/controller/mcp_log_canary_test.go
Comment thread pkg/receipts/lock_other.go Outdated
Comment thread pkg/dataset/features.go
Comment thread pkg/receipts/trustroot.go Outdated
Comment thread CHANGELOG.md Outdated
Comment thread ROADMAP.md Outdated
Comment thread docs/SHOWCASE-DEMO-WALKTHROUGH.md Outdated
Comment thread docs/security/threat-model.md Outdated
Redact credential and personal values from action-derived log fields and
status messages. Validate that a 64-byte Ed25519 key matches its seed in both
loaders. Fail receipt chain locking on non-unix builds. Allowlist fake
credential fixtures in the secret scanner. Correct INV-06, OPA, threat model
and stale replace notes in docs.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Receipt records and one threshold log path can still expose credential-bearing action text.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (8)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Redact sensitive action text before creating signed receipts

pkg/​receipts/​record.go:341

Action text is copied into the record and later into the signed receipt. A denied action can contain detected credentials or personal data. This contradicts the receipt privacy guarantee because INV-01 actions are still receipted. Redact or hash action-derived fields and threshold reasons before building the record.

Comment thread internal/controller/mcp_log_canary_test.go Outdated
…gsafe test fixtures

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Approval binding, trust-root handling, secret scanning, and writer network isolation have unresolved security gaps.

Review effort: Balanced
Findings: 3 High severity · 1 Medium severity

Open (4)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Prevent broad egress policy overlap for the signing-key pod

charts/​templates/​_helpers.tpl:110

This label also matches the broad egress policy selector in charts/templates/networkpolicy.yaml:38-40. Kubernetes unions allowed egress across matching policies. The writer's separate egress: [] therefore does not isolate its signing-key pod from DNS and platform services. Exclude this component from the broad policy or avoid the matching label.

Comment thread api/v1alpha1/agentworkload_admission.go
Comment thread cmd/agentctl/dataset.go
Comment thread cmd/agentctl/decision.go Outdated
Comment thread scripts/scan_secrets.sh Outdated
…t allowlist

The webhook rejects changes to declaredIntent, policyPacks, opaPolicy,
decisionModel and approvalCapture while an approval is pending. agentctl
dataset verify requires a pinned trust root, and decision eval counts data as
verified only with one. The secret scanner allows exact fixture values instead
of whole files.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Direct approvals, receipt settings, network isolation, and secret scanning contain unresolved safety gaps.

Review effort: Balanced
Findings: 3 High severity

Open (3)
Resolved since last review (4)
Previously missed (1)

In code that hasn't changed since last review

Low severity Update outdated v0.1.4 build guidance

scripts/​demo/​Dockerfile.demo:4

This comment says v0.1.4 is untagged and the root Dockerfile cannot build. BLOCKERS.md:14-17 states v0.1.4 is pinned and that Dockerfile now builds. Update this explanation to avoid contradictory build guidance.

Comment thread charts/templates/_helpers.tpl Outdated
Comment thread charts/values.yaml
Comment thread pkg/agentctl/approve.go
…ect required without enabled

The writer pods no longer carry part-of agentic-operator, which the broad
egress allow policy selects. NetworkPolicy rules add up, so the writer's own
deny could not revoke it. global.receipts.required=true now fails the render
unless enabled=true, so the operator cannot run with receipts silently off.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Secret-scan bypasses and receipt-client desynchronization can undermine CI and receipt availability.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (3)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Redact JSON map keys with collision-safe placeholders

pkg/​dataset/​dataset.go:227

Redacted capture preserves map keys verbatim. An email or credential used as a JSON key is written raw to approvals.jsonl. Redact keys too, with a collision-safe placeholder scheme.

Low severity Align object-presence checks with IsZero behavior

docs/​policy-input.md:31

This says object presence activates the checks, but Declared.IsZero skips them when every field is empty. Align this with the implemented and invariant-documented rule.

Comment thread pkg/receipts/remote.go
Comment thread scripts/scan_secrets.sh Outdated
…whole tokens

The remote writer client treats the head as unknown from the POST until a
verified reply extends it, so a lost reply forces a resync instead of stacking
another receipt on a stale head. The secret scanner removes fixture values
only when they are whole tokens, so a longer token that starts with one still
fails.

Signed-off-by: Shreyansh Sancheti <007ssancheti@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Raw action data can leak into receipts and status, while secret scanning still bypasses one path.

Review effort: Balanced
Findings: None

Resolved since last review (2)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Redact untrusted MCP action names in workload status

internal/​controller/​reservation.go:28

The reservation condition persists the untrusted MCP action name without redaction. Credential or personal data in the name will remain in workload status. Apply safeLogText here too.

Medium severity Remove approved proposals to prevent duplicate action counting

pkg/​decision/​observe/​observe.go:63

Approved actions remain in ProposedActions with Approved=false and are also appended to ExecutedActions. This counts one approved action twice and permanently increments prior_denied, skewing model features. Update or remove the matching proposal when approval succeeds.

Low severity Redact sensitive action fields before decision receipt creation

CHANGELOG.md:46

This privacy claim is false. Decision records store Action, Observed.Tool, and threshold reasons verbatim. An MCP action containing a credential is therefore persisted in signed JSONL, even when INV-01 denies it. Hash or redact these fields before receipt creation.

@shreyanshjain7174
shreyanshjain7174 merged commit 4266c6d into main Oct 2, 2026
8 checks passed
@shreyanshjain7174
shreyanshjain7174 deleted the feat/decision-arch branch October 2, 2026 05:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants