Write policies
Approve versioned mutation semantics separately from actor authorization
A write policy states how a supported destination may change records: append, replace, upsert, update or delete; identity keys; duplicate/conflict handling; omissions; schema changes; retention and commit boundaries. It is a versioned contract, not a grant to a person and not proof that every connector implements every option.
You need a Semogram account with applicable policy permissions. Owners/admins obtain the write-policy permissions through their role; Members need explicit grants. API callers need appropriate scopes, resource reach and an accountable current member for the governed policy path.
Lifecycle
| Stage | Effect |
|---|---|
| Propose | Save a new immutable contract revision and reason |
| Review | Inspect semantics, scope, inherited constraints and destination support |
| Approve/reject | Record a decision for the selected revision |
| Bind | Select an approved revision for a supported pipeline node/endpoint |
| Execute | Recheck policy state, actor permission and supported behavior |
| Suspend/retire | Make the revision unavailable for future resolution/execution checks |
Approval does not run a pipeline. Applying a policy to a pipeline changes its draft; review, validate and save the draft before execution. A suspended parent can also prevent an inherited child from resolving.
Example: keyed equipment output
Assume a dedicated equipment output target supports keyed upsert on equipment_id. Incoming records represent changes; omitted records should remain. Create a proposal named Equipment output updates with:
| Control | Value |
|---|---|
| Incoming data | Changes since the last read (delta) |
| Allowed operation | Add or update by identity (upsert) |
| Identity columns | equipment_id |
| Duplicate input | Reject |
| Conflicting values | Allow overwrite |
| Missing records | Preserve |
| Schema changes | Reject |
| Retention | 30 days; no physical deletion |
| Concurrent writes | Reject |
| Commit boundary | Row, only if supported by this destination |
| Invalid records | Reject |
Confirm a real database primary key and the installed writer's semantics. Choose the actual supported commit boundary; a policy cannot create run-level atomicity on a row writer.
Open Settings → Write Policies → New policy. Choose workspace or project scope, fill the manual controls (or review an assistant proposal), add a reason and save. Open the exact revision, inspect its summary and approve with a reason if authorized. Use the apply page to choose an approved revision, project, pipeline and destination node. Validate/save the changed pipeline draft and test a bounded write.
Prepare an Equipment output updates policy for the supported equipment writer. Use delta input, keyed upsert on equipment_id, reject duplicates, permit overwriting matching values, preserve omitted records, reject schema changes and forbid physical deletion. Inspect actual destination atomicity before selecting a commit boundary. Show the proposed scope and settings for review before saving or approving.The in-app policy author supports proposals. Read-only policy MCP tools inspect existing records; they do not propose/approve revisions.
Public routes exist: POST /api/v1/write-policies proposes a complete {name, projectId, policy, parent, reason} body; PATCH /api/v1/write-policies/<POLICY_UUID> records a decision with {version,status,reason,destructiveApproval}. GET collection/detail inspects records. POST /api/v1/write-policies/<POLICY_UUID>/bind takes planId, nodeId and version to alter a supported pipeline draft.
For the dedicated equipment example, this complete proposal body uses workspace scope. Choose a real supported target/atomicity before submitting. A proposal records semantics; it does not select a database connection.
{
"name": "Equipment output updates",
"projectId": null,
"parent": null,
"reason": "Update equipment by its primary key; preserve other records and disallow deletion.",
"policy": {
"contractVersion": 1,
"sourceState": "delta",
"mutation": "upsert",
"identity": { "keys": ["equipment_id"], "nullKeys": "reject" },
"duplicates": "reject",
"conflicts": "overwrite",
"omission": "preserve",
"emptySnapshot": "reject",
"relationships": "preserve",
"schemaEvolution": "reject",
"partitionEvolution": "reject",
"retention": { "minimumDays": 30, "physicalDelete": false },
"compaction": "disabled",
"concurrency": "reject",
"atomicity": "row",
"invalidRows": "reject"
}
}Read the returned id/version. To approve that exact version, PATCH its policy URL with { "version": <returned version>, "status": "approved", "reason": "Reviewed against the equipment writer and bounded fixture.", "destructiveApproval": true }. Upsert is classified as destructive because it can overwrite existing records; approval requires explicit destructive approval even though physical deletion is disabled. Only supply this approval and reason after reviewing those effects. Then bind its returned version to the actual pipeline/node and validate/save the changed draft.
Use policy:propose for proposals, policy:approve for approve/reject and policy:manage for suspend/retire. Binding also needs applicable pipeline-authoring access. The contract reference supplies complete policy fields; a fragment is not a full proposal request.
Use write_policy_list / write_policy_get to inspect policies, revision states and decision history. Workspace-only policies can be listed with workspaceOnly true. These tools require policy:read. For a supported endpoint, source_write_policy_bind is a distinct binding action with its own management checks.
Important boundaries
An authorized proposer with policy:approve can approve their own proposal; the current system does not enforce a different reviewer. Destructive combinations need explicit destructiveApproval. Mutation permissions such as data:write/data:delete/data:maintain are checked separately.
Workspace-wide policies can be used where supported across the workspace. Project policies remain in their project. Data endpoint source-write policy bindings require workspace-wide policies. A new revision does not silently update consumers pinned to the old revision.
Policy inheritance cannot weaken protected parent rules and rejects cycles/excessive depth. Inspect the entire applicable chain before approving. See Consumer access for workspace modes and Source write grants for caller-specific endpoint authorization.