Mutation Integrity
Tracker references: pm-h90s, pm-pim7, pm-w8q4, pm-9yfo, pm-wwooxx, pm-3kk6
pm treats mutation provenance and content safety as shared SDK policy. CLI,
MCP, and package hosts can therefore enforce the same rules without duplicating
argument parsing or exposing detected values.
Agent Quick Context
- Every mutation should have a stable author. Precedence remains explicit
invocation author,
PM_AUTHOR, configuredauthor_default, detected harness identity, thenunknown. - The secret guard inspects string leaves and returns only detector names and object paths. It never returns the matched value.
pm healthreports old, unclaimed in-progress work as advisory governance data. It does not change ownership or status.- Historical unknown-author events remain immutable. Maintainers can append an audited acknowledgment rather than rewriting their original JSONL streams.
Workspace Policy
Configure policy through pm config; do not hand-edit settings.json.
pm config project set mutation_guard_require_attributed_author true
pm config project set mutation_guard_secret_guard block
pm config project set mutation_guard_stale_in_progress_hours 48
Defaults preserve author compatibility while providing secret advice:
| Setting | Default | Behavior |
|---|---|---|
mutation_guard.require_attributed_author |
false |
When enabled, reject an effective author of unknown. |
mutation_guard.secret_guard |
advise |
off, stderr/structured advise, or pre-write block. |
mutation_guard.stale_in_progress_hours |
72 |
Age threshold for unclaimed active work in pm health. |
A blocking secret policy may be bypassed only by an explicit --force on a
force-capable mutation. The warning records that an override occurred while
remaining fully redacted.
SDK Use
Package hosts can apply the exact runtime policy before dispatch:
import {
evaluateMutationGuard,
inspectStaleInProgressItems,
scanMutationSecrets,
} from "@unbrained/pm-cli/sdk";
const guarded = evaluateMutationGuard({
author: "package-agent",
payload: mutationInput,
settings: projectSettings.mutation_guard,
});
const findings = scanMutationSecrets(mutationInput);
const stale = inspectStaleInProgressItems(items, {
in_progress_status:
projectSettings.schema.workflow.in_progress_status ?? "in_progress",
threshold_hours: projectSettings.mutation_guard.stale_in_progress_hours,
});
evaluateMutationGuard returns stable warning codes, redacted findings, and
whether a blocking policy was explicitly overridden. scanMutationSecrets is
available separately for package-specific preflight UIs. Cyclic inputs are
safe; scanner failures fail open with secret_guard_scan_failed_open.
The SDK guard and repository scanner consume one inventory in
src/core/shared/secret-rules.ts. It covers GitHub, npm, Slack, AWS access
keys (including temporary keys), Google, Anthropic, OpenAI, GitLab, Sentry,
private keys, JWT-shaped tokens, npm authentication assignments, password
assignments, inline sshpass passwords, private IPv4 addresses, private SSH
hosts, and absolute home-directory paths. Every repository rule also runs
against mutation string leaves in advise and block modes.
Two repository fixture exemptions apply only to tests/ and examples/:
absolute home paths and user-at-private-host matches. They never exempt
mutation content. The inventory explicitly records one mutation-only heuristic:
high-entropy assignments. Source identifiers can resemble generic assignments,
so that heuristic does not run over repository source text. No rule is silently
gate-only. Repository diagnostics retain hyphenated names; SDK findings retain
underscore names. Adding a pattern therefore changes both consumers together.
A private address or host path can be legitimate project context. The default
remains advisory; a workspace selecting block must replace those literals
with references or explicitly review a force override. CLI routing paths and
payload content are distinct: pass only content to package-owned preflight.
Nested settings reads use own properties. Writes reject empty and prototype path segments before changing any object, create local containers for inherited values, and define own data properties without invoking inherited setters. Findings expose only the stable rule name and object path. Matched credential text is never returned, logged, or included in a recovery bundle.
Executable mutation inventory
PM_MUTATION_ACTION_CONTRACTS is the public, typed inventory of core mutation
actions. Each entry identifies whether the action must append item history,
workspace history, or both. Package hosts and quality gates should derive
mutation coverage from this contract instead of maintaining a second command
list.
import { PM_MUTATION_ACTION_CONTRACTS } from "@unbrained/pm-cli/sdk";
const itemHistoryActions = PM_MUTATION_ACTION_CONTRACTS.filter(
(contract) => contract.history_scope === "item",
);
The contract is a classification primitive, not proof that every handler actually wrote its required event. The history-completeness release gate must execute every classified action against a sandbox and assert the resulting operation and author before the repository can treat that invariant as fully enforced. Workspace actions remain explicitly classified while that executable gate is completed.
Linked-Test JSON Assertions
Tracked by pm-czr31p.
JSON assertions accept dotted fields and non-negative safe integer indexes,
such as result.items[0].count or [0][1].count. The whole path must match:
empty segments, empty brackets, negative indexes, and omitted separators fail
the assertion instead of silently selecting a different field. Traversal reads
only own data properties and never invokes getters or follows prototypes.
Unknown-Author Disposition
Use scanHistoryAuthorAttribution to obtain exact item and one-based line
coordinates. After evidence-backed review, SDK hosts can call
acknowledgeUnknownAuthorHistoryEvents. The function validates that every
target is still an actionable unknown-author event and appends
history:author-acknowledge to _workspace.jsonl.
The same primitive is available as
pm history-author-acknowledge --event <item-id:line> ...,
PmClient.historyAuthorAcknowledge, and the MCP pm_run action
history-author-acknowledge. See
Agent Runtime Primitives for the shared
CLI/SDK/MCP contract.
The acknowledgment includes target coordinates, the attributed principal, reviewer, and rationale. Subsequent health and validation scans keep the unknown event in immutable totals but remove it from actionable warnings.
Stale In-Progress Governance
scanStaleInProgressItems combines item metadata with the latest valid history
timestamp. An item is reported only when all three conditions hold:
- its normalized status matches the configured in-progress status;
- it has no assignee;
- its last activity is at least the configured threshold old.
The health result includes deterministic item ordering, age, last activity,
threshold, and remediation. Resolve each finding by claiming genuinely active
work or returning abandoned work to open. A direct
pm update <id> --status in_progress also emits a stderr-only advisory when
the resulting item would have no assignee; use pm claim <id> to make active
ownership explicit. Machine-readable --json invocations suppress this
presentation advisory so stderr remains a single parseable error envelope.