Declarative workflow policies

Tracker: pm-mj42og pm-htbetn, pm-khd2fd, and pm-ycv2cz.

Projects can describe their lifecycle requirements as versioned data. The SDK uses the same evaluator for item mutations, policy previews, and completeness reports. Existing projects have no implicit policies. A declaration can advise, warn, or refuse; refusal also requires an explicit workspace opt-in.

Configure a requirement

pm schema policy-put close-evidence --definition '{"effect":"refuse","subject":{"statuses":["closed"]},"rule":{"kind":"require_fields","fields":["resolution","actual_result"]}}'
pm schema policies
pm schema policy-check pm-example --definition '{"status":"closed"}'
pm schema policy-mode refuse

Use canonical status ids and metadata field names. body selects the Markdown body. Custom fields can use dotted paths with at most eight segments. Empty strings, arrays, objects, null, and absent values do not satisfy required fields; zero and false are meaningful values.

policy-put accepts the id once: pass it as the operand, or omit the operand and include id in the definition. When both are supplied their normalized ids must agree; a mismatch refuses without changing the registry or history. The same normalization applies to JSON strings, objects, previews, SDK, and MCP. policy-put replaces a declaration with the same id. policy-remove <id> removes one declaration. policy-mode advise disables refusal while retaining diagnostics. Registry writes are locked and recorded in workspace history. --dry-run previews registry changes without writing. Malformed registries fail closed.

Selectors and rules

All selectors in subject must match; values within one selector are alternatives. Supported selectors are types (case insensitive), tags, statuses, operations, parents, and dependency: { "kind": "implements", "id": "pm-example" } (id optional). Parent and dependency selectors inspect direct relationships. Status selectors match the destination; other selectors inspect both snapshots so a mutation cannot escape a protected scope by removing its tag or changing its type.

Rule kind Required properties Behavior
require_fields fields Checks evidence on lifecycle, scope, or required-field changes
transition allowed Allows listed [from, to] status pairs; $create and $delete represent absent records
authors authors Requires a listed mutation actor
field_writers fields, authors Requires a listed actor when protected fields change
approval fields, authors Requires independent approval when an existing item changes status or is deleted

Operation selectors use exact history operation names. Unknown declaration keys are errors. Policies contain no executable expressions or regular expressions. At most 256 declarations and 64 terms per selector/rule are accepted. Registry reads are bounded to 1 MiB, and approval-history reads to 4 MiB.

Actor names are provenance supplied by the caller or local configuration. They are not authenticated identities, and these rules are not an access-control boundary against a caller who can edit the tracker or choose an author name. Authenticated, signed identity (pm-u14c) is a prerequisite for stronger authorization guarantees.

Approvals

Prepare the declared fields, then have a different workflow actor record approval:

pm schema policy-put review --definition '{"id":"review","effect":"refuse","subject":{"statuses":["closed"]},"rule":{"kind":"approval","fields":["body","resolution"],"authors":["reviewer"]}}'
pm schema policy-approve pm-example --policy review --author reviewer --message 'Reviewed acceptance evidence'

Approval events bind the item id, selected field values, and complete policy revision. Changing reviewed content or the declaration invalidates the approval. Unrelated metadata changes do not. The actor performing the transition must differ from the approval actor. Only sealed approval events from a verified item history chain count. policy-check previews transitions; policy-approve always records an event and rejects --dry-run.

Approval rules apply to existing items. Creation and initial imports have no reviewable history and skip approval requirements; use transition rules with ["$create", "open"] to constrain their initial status. Deletion requires approval of the current record, using the same content and independent-actor checks.

Refused mutations preserve item state and item history, and append a workspace refusal event. Accepted mutations attach bounded policy decisions to their normal history event. Diagnostics contain policy ids and missing field paths, without copying reviewed content into errors. If the mandatory refusal audit cannot be appended, the mutation instead raises workflow_policy_audit_failed and preserves the original failure in the SDK error's cause. Item data remains unchanged; resolve the workspace history lock or storage failure before retrying. Package imports do not downgrade this error to an item-lock warning.

Completeness reports and SDK

pm schema policy-presets
pm validate --check-completeness --strict-exit

Presets return optional advisory declarations for familiar item types. They do not install policies. Author the declarations appropriate to the project's own types and lifecycle using policy-put. Completeness reports group counts by type, return bounded violation examples, and use only require_fields declarations without subject.operations. The report identifies this scope as operation_independent_require_fields. All item selectors, including status, still apply. policy-check without a proposed definition uses the same state-only scope. Operation-scoped rules remain enforced during matching mutations; a clean state report does not imply permission to perform every future operation. To check a lifecycle field invariant in both state reports and mutations, select its statuses without restricting operations. Default validation includes this check; projects without declarations remain unaffected. An unreadable or malformed schema/policies.json produces an error-level completeness result with a repair hint. Other requested validation checks still run, and --strict-exit fails until the policy registry is repaired.

The published @unbrained/pm-cli/sdk/governance entrypoint exports runWorkflowPolicyAction, evaluateWorkflowPolicies, policy types, parsers, WORKFLOW_POLICY_SCHEMA, and WORKFLOW_POLICY_DOCUMENT_SCHEMA. PmClient exposes workflowPolicy(action, name, options). CLI schema and MCP pm_schema use the same action vocabulary and SDK implementation; MCP options accept camelCase keys and structured JSON definitions.

For repeated pure evaluation, createWorkflowPolicyEvaluator(document) returns an evaluator that owns a private normalized policy snapshot and hashes each declaration once. Later edits to the supplied document do not change that snapshot; create another evaluator to adopt them. Completeness validation uses one such snapshot for the whole supplied corpus.

The schema contract version is 4.19; policy-put requires definition but no longer requires a separate name. Existing schema operations remain available; SchemaResult now also includes WorkflowPolicyActionResult. Consumers that exhaustively narrow schema results should handle the policy_result: true discriminant. Validation options include checkCompleteness, and validation results can contain a completeness check.

Require a meaningful completeness contract

A successful completeness check means no applicable field requirement was violated. It does not establish that a project declared any requirements. The receipt exposes these independent measurements even in counts mode:

Field Meaning
declared_requirement_count Number of require_fields declarations, including operation-specific ones
state_requirement_count Number eligible for a state scan, without operation selectors
applicable_requirement_count Distinct requirements that matched at least one supplied item
requirement_application_count Item/requirement pairs actually evaluated
governed_items, ungoverned_items Items with and without a matching state requirement
contract_status undeclared, empty, inapplicable, partial, or covered

applied_policy_ids is bounded by the diagnostic row limit; its truncation flag and applicable_requirement_count retain the complete total. covered describes applicability, not compliance: violations are reported independently. Unreadable source data still fails the check, regardless of the coverage of readable items.

Projects can require a nonempty applied contract with the existing assurance primitives. Replace pm-example with the accountable item in your workspace:

pm assurance put measurement completeness-contract --definition '{"id":"completeness-contract","source":{"kind":"validate","check":"completeness","field":"applicable_requirement_count"}}'
pm assurance put assertion completeness-contract-required --definition '{"id":"completeness-contract-required","measurement_id":"completeness-contract","owner_item_id":"pm-example","scope":{"kind":"all"},"floor":1,"lifetime":"hold","enforcement":"block","negative_control":{"cases":[{"observed":0,"expected":"fail"},{"observed":1,"expected":"pass"}]}}'
pm assurance put gate completeness-contract --definition '{"id":"completeness-contract","assertion_ids":["completeness-contract-required"],"triggers":["ci"]}'
pm assurance run completeness-contract --trigger ci --dry-run
pm ops validate --check-completeness --strict-exit

Run both checks: the gate requires applicability and validation checks compliance. Deleting the last applicable policy fails the gate. For an empty workspace, or a contract intended only for a future lifecycle state, measure state_requirement_count instead. To require coverage of every current item, also enforce a zero bound on ungoverned_items. Higher per-project floors can protect against partial policy deletion; a floor of one only proves at least one requirement applies.


Workflow_policies remote
报告问题