# Constraints for signet-eval Gate and Ensure actions
# Generated from constrain interview + manual refinement

- id: C001
  boundary: Evaluation Determinism
  condition: Gate and ensure actions produce the same result given the same inputs and system state.
  violation: Non-deterministic evaluation leads to unpredictable allow/deny decisions.
  severity: must
  rationale: >
    signet-eval's core guarantee is deterministic evaluation. Gate checks a static log.
    Ensure runs a script whose output depends on system state, but given the same state,
    the same script must produce the same result. No randomness, no network calls in the engine.
  affected_components: [policy.rs]

- id: C002
  boundary: No Side Effects in Evaluation
  condition: >
    Policy evaluation (gate log query, ensure script execution) must not modify signet-eval
    internal state. The ensure script may modify external system state (e.g., gh auth switch
    via user interaction), but signet-eval's own data (policy, vault, ledger) remains untouched
    during evaluation.
  violation: Evaluation with side effects breaks the idempotency guarantee and could corrupt state.
  severity: must
  rationale: >
    The current evaluate() function is pure. Gate adds a read-only query to the vault ledger.
    Ensure spawns a subprocess but does not write to the vault or policy during evaluation.
    The action is logged AFTER evaluation completes, in hook.rs, not in policy.rs.
  affected_components: [policy.rs, hook.rs]

- id: C003
  boundary: Check Script Sandboxing
  condition: >
    Ensure scripts are resolved only from ~/.signet/checks/ by name. The script name must be
    a simple filename: no slashes, no "..", no null bytes, no control characters, no unicode
    normalization tricks. Path is constructed as: checks_dir.join(name), then canonicalized
    and verified to still be inside checks_dir.
  violation: Path traversal allows execution of arbitrary scripts outside the protected directory.
  severity: must
  rationale: >
    The policy YAML is the attack surface. A rule with ensure.check="../../../tmp/evil" must
    be caught at validation time (policy validate) and at runtime (evaluate). Defense in depth:
    validate the name AND canonicalize the resolved path.
  affected_components: [policy.rs]

- id: C004
  boundary: Check Script Timeout
  condition: >
    Ensure scripts execute with a hard timeout (default 5 seconds, configurable per-rule via
    ensure.timeout field, max 30 seconds). On timeout, the subprocess is killed (SIGKILL after
    SIGTERM grace period) and the rule evaluates to deny.
  violation: A hung or malicious script blocks the hook indefinitely, making the AI agent unresponsive.
  severity: must
  rationale: >
    Hook mode is synchronous -- Claude Code waits for the response. A script that hangs
    (e.g., waiting for network, deadlocked) must not block indefinitely.
    Note: legitimate scripts may need user input, so the timeout should be generous enough
    for interactive prompts (10s recommended for interactive scripts).
  affected_components: [policy.rs]

- id: C005
  boundary: Checks Directory Protection
  condition: >
    A locked self-protection rule protects ~/.signet/checks/ from AI agent modification.
    This rule must be: (a) the highest priority locked rule (inserted before all other
    self-protection rules), (b) enforced even during pause mode, (c) un-removable and
    un-editable via MCP tools, (d) included in the hardcoded default_policy() fallback.
  violation: Agent modifies check scripts to bypass ensure rules, defeating the enforcement mechanism.
  severity: must
  rationale: >
    The ensure system's security depends on the integrity of check scripts. If the agent
    can write to ~/.signet/checks/, it can replace a failing check with "exit 0" and bypass
    any ensure rule. This is the same threat model as the existing self-protection rules.
    Making it highest priority means it's checked before any other rule, including pause logic.
  affected_components: [policy.rs, hook.rs, mcp_server.rs]

- id: C006
  boundary: Backward Compatibility
  condition: >
    Existing policies with allow/deny/ask rules continue to work unchanged. The new gate/ensure
    fields are optional and only relevant when action is Gate or Ensure. Deserialization must
    handle policies that don't have these fields. Serialization must skip them when not present.
  violation: Upgrading signet-eval breaks existing policies.
  severity: must
  rationale: >
    Users have existing policy.yaml files. The new Decision variants and struct fields must
    be additive. serde skip_serializing_if ensures clean YAML output for existing rules.
  affected_components: [policy.rs]

- id: C007
  boundary: Gate Log Query Scope
  condition: >
    Gate's "within" parameter specifies how many recent log entries to search (default 50,
    max 500). The query checks the detail column of the ledger for a substring match of
    requires_prior. Only entries with decision="allow" are considered (denied commands don't
    count as "executed").
  violation: Unbounded log scan degrades performance. Including denied entries gives false positives.
  severity: should
  rationale: >
    The ledger can grow large over time. Bounding the scan prevents performance degradation.
    Only allowed commands were actually executed, so denied entries should not satisfy the gate.
  affected_components: [policy.rs, vault.rs]

- id: C008
  boundary: Gate Without Vault
  condition: >
    If no vault is available (user hasn't run signet-eval setup), gate rules evaluate to deny
    with a clear message explaining that gate requires a vault for action log access.
  violation: Gate silently allows when it can't check the log, defeating its purpose.
  severity: must
  rationale: Fail-closed. No vault means no log to check, so the prerequisite can't be verified.
  affected_components: [policy.rs]

- id: C009
  boundary: Policy Validation for New Actions
  condition: >
    signet-eval validate must check: (a) gate action has gate config with requires_prior,
    (b) ensure action has ensure config with check, (c) ensure.check is a valid simple
    filename, (d) ensure.timeout if present is between 1 and 30, (e) gate.within if present
    is between 1 and 500. Missing or invalid configs are validation errors.
  violation: Invalid policies loaded at runtime cause panics or undefined behavior.
  severity: must
  rationale: Catch errors at validation time, not at evaluation time.
  affected_components: [policy.rs]

- id: C010
  boundary: Ensure Script Stderr Capture
  condition: >
    When an ensure script fails (non-zero exit), signet-eval captures stderr (truncated to
    500 bytes) and includes it in the deny reason alongside ensure.message. This gives the
    agent actionable context about why the check failed.
  violation: Agent receives a generic "check failed" with no guidance on what to fix.
  severity: should
  rationale: >
    The script knows why it failed (e.g., "active user is jmc-wander but remote is jmcentire").
    Relaying this to the agent helps it take corrective action voluntarily.
  affected_components: [policy.rs, hook.rs]

- id: C011
  boundary: Git Remote Identity Matching
  condition: >
    The gh-identity-matches-remote check script must handle: (a) remote URL contains
    "jmcentire" -> require jmcentire account, (b) remote URL contains "wandercom" ->
    require jmc-wander account, (c) remote URL contains neither -> prompt user to choose,
    (d) not inside a git repo (e.g., gh commands without a repo context) -> pass through
    with a warning, (e) multiple remotes -> check the remote being targeted (default: origin).
  violation: Wrong-account operations proceed unchecked.
  severity: must
  rationale: This is the motivating use case. The script must cover all realistic scenarios.
  affected_components: [checks/gh-identity-matches-remote]

- id: C012
  boundary: MCP Tool Support
  condition: >
    signet_add_rule and signet_edit_rule must accept gate and ensure config fields.
    signet_list_rules must display gate/ensure config when present. signet_condition_help
    must document the new action types. MCP tools must not allow creating ensure rules
    that reference scripts outside the checks directory.
  violation: Users can't manage gate/ensure rules through the conversational MCP interface.
  severity: should
  rationale: >
    The MCP server is the primary interface for policy management. It must support the
    full lifecycle of gate/ensure rules.
  affected_components: [mcp_server.rs]

- id: C013
  boundary: Documentation of Latency Impact
  condition: >
    Documentation and condition_help must clearly state that ensure scripts add latency
    to hook evaluation. Recommend lightweight scripts (sub-second) for high-frequency
    conditions. Suggest gate as the lighter-weight alternative when a log check suffices.
  violation: Users add expensive ensure checks without understanding the performance impact.
  severity: should
  rationale: >
    Ensure scripts can take up to 30 seconds. Users must understand the trade-off.
    Gate is nearly free (SQLite query). Documentation should guide users toward the
    appropriate action type.
  affected_components: [documentation]
