На этой странице
- Write a Change Proposal
- The format is mandatory
- The procedure
- 1. Find the document, do not invent one
- 2. Write the FULL new body
- 3. Validate before you propose
- 4. Propose
- 4a. Work the draft out (spec-first projects only)
- 5. STOP if approval is required
- 6. When it comes back rejected
- 7. Apply with the work, not before it
- When the change is not a spec change
- Checklist
flownix-write-spec
Use whenever you are about to record or change described behaviour in a Flownix project — writing a feature's spec, updating a doc node, or preparing the delta a task needs to close. Triggers on "write the spec", "update the spec", "propose
Write a Change Proposal
The one place that says how behaviour is written down in Flownix, and what happens between writing it and being allowed to build it. Every other skill points here instead of restating the format — three copies of a rule drift, and the drifting copy is always the one someone reads.
Prerequisite: flownix-basics.
The format is mandatory
Behaviour is recorded in the OpenSpec format. Not preferred, not the default you may fall
back from: it is the only accepted way to describe what the system does. Freeform prose in a
doc node is a defect — validate_spec reports it, spec_mode=strict refuses any delta that
adds a new violation, and the export to openspec/ has nothing to round-trip.
# <Document title>
## Purpose
One or two sentences: what this part of the system is for.
## Requirements
### Requirement: <name>
The system SHALL <one observable behaviour>.
#### Scenario: <the case this covers>
- GIVEN <starting condition>
- WHEN <what happens>
- THEN <the observable result>
## Non-goals
- <what this deliberately does not do, and why>- One requirement, one behaviour, one
SHALL. Three "and also" clauses are three requirements. A requirement you cannot fail is not a requirement. SHALL/MUSTfor a hard requirement,SHOULDwhere a justified exception exists,MAYfor genuinely optional. Uppercase — that is what makes them RFC 2119 keywords.- Every requirement needs at least one
#### Scenario:with GIVEN/WHEN/THEN. A requirement nobody can check is a wish, not a contract. - Keep the how out. Queues, libraries, table schemas, file names belong in the plan. The spec survives three rewrites of the implementation; anything that would not is in the wrong document.
- Write
## Non-goalswhen a boundary was a decision. "We deliberately do not do X" is the part future readers most often need and least often find. - If you cannot phrase the change as a requirement with a checkable scenario, you have not yet worked out what the behaviour is. That is the thing to fix — not the format.
The procedure
1. Find the document, do not invent one
rag_query for the behaviour you are about to describe, then list_docs / get_node. A second
document about the same subject is worse than a stale one: the reader finds one of them and
never learns the other exists. Create a doc node only when nothing covers the area
(create_doc_node, bound to at least one component).
2. Write the FULL new body
propose_doc_delta takes the complete resulting document, never a patch, never an excerpt,
never just the section you touched. Applying a delta replaces the body wholesale, so a fragment
silently collapses the spec to that fragment — a data loss noticed weeks later.
Read the current body (get_node), keep everything still true, make your change, send the whole
thing back.
3. Validate before you propose
validate_spec(doc_slug: "<DOC>", body: "<your full body>")Changes nothing, tells you what is wrong and where. In spec_mode=strict a delta that adds a
violation the previous version did not have is refused; violations inherited from the previous
version come back as warnings and do not block — the format tightens as documents are edited,
not in one sweep. Fixing the inherited ones while you are in there is welcome, but never
required, and never at the cost of rewriting text you did not come to change.
4. Propose
propose_doc_delta(doc_slug: "<DOC>", source_node_slug: "<TASK or PLAN>", body: "<full body>",
reason: "<why the behaviour changes, one or two sentences>")The response carries ops — which requirements this adds, changes, removes or renames — and
spec_issues. Read ops: if it says you removed a requirement you did not mean to touch, you
dropped it while rebuilding the body.
In a project with spec_first on, the delta is born review_status: draft. A draft asks
nobody anything: it is your working copy, and telling the user their approval is pending would
leave you both waiting for each other. The question is put by submit_doc_delta, in step 4a.
4a. Work the draft out (spec-first projects only)
update_doc_delta(revision_id, body: "<full body>") replaces the body in place, keeping the
delta's identity and its source_node. Call it as many times as the document needs.
bodyis the full document here too. The temptation is stronger than when proposing, because "update" sounds like it should take the paragraph you fixed. It does not: applying a delta replaces the document wholesale.- Format issues do not block the edit, even new ones. A draft is written in passes, and a
requirement whose scenario is not written yet is a normal intermediate state. The ratchet
applies at
submit_doc_deltaand atapply_doc_delta. - Record rejected decisions as
[decision]comments on the source node. What a discussion threw away is the part a future reader most needs and least often finds.
Then submit_doc_delta(revision_id) — the only draft → pending transition. After it the delta
is in the review queue, holds the gates, and can no longer be edited in place. The whole
procedure around this, from opening a change to cutting tasks, is spec-first-change.
5. STOP if approval is required
Read get_project_policy. When require_proposal_approval is on (it only means anything with
spec_mode: strict), the proposal is a gate, not a step:
- The delta comes back
review_status: pending. - Until a human makes it
approved, the server refuses to move the covered task or plan intoin_progress, and refusesapply_doc_delta. Both refusals areFailedPreconditionprefixedproposal_not_approved. The gate looks up the tree: a delta proposed from a feature or plan blocks the tasks underneath it.
You cannot approve it. approve_doc_delta and reject_doc_delta refuse every call from an
agent session with PermissionDenied — they exist in your toolchain so you can name the step,
not take it.
So: stop, tell the user, wait. Name the delta and the document, say what the proposal changes in one or two lines, and say plainly that it needs their approval in the web UI before you can start. Then stop — do not fill the wait with adjacent work you were not asked for.
What not to do while waiting, all of which look like progress and are not:
- re-proposing the same delta (a second pending row, same gate);
- editing the draft after
submit_doc_delta— the server refuses it, and the point is that you would be changing the text the human is looking at while they look at it; - applying it (refused);
- moving the task into
in_progressanyway (refused); - deleting it and starting over — a rejected delta can still be approved later, and deleting it destroys the review trail;
- switching to
spec_mode: offor turning the policy off to get past the gate. Changing the project's policy to avoid its policy is not a workaround, it is the failure.
Resume when list_doc_deltas shows review_status: approved.
6. When it comes back rejected
Read review_note — the reason is mandatory on the server precisely so it exists. Rebuild the
spec on that feedback. Do not argue with the rejection in a comment and re-send the same body.
Where you put the rebuilt text depends on the project:
spec_firston — fix the same delta withupdate_doc_delta. It returns todraft, the rejected body and the reason stay readable in its round history, andsubmit_doc_deltasends it back. A new delta would break that thread: the point of the history is that "what we discarded and why" reads as one story on one row.spec_firstoff — there is no draft state and no round history, so propose a new delta, as before.
7. Apply with the work, not before it
In spec_mode: strict, apply_doc_delta belongs with closing the task: the spec says what the
system does, and it starts saying it when the system actually does it. A version conflict
(version_conflict) means the document moved under you — reread it, rebuild your change on the
current body, propose again.
When the change is not a spec change
Not every task touches described behaviour. declare_no_spec_impact exists for exactly that,
and it wants a sentence naming what you changed and why no documented behaviour moved — a
placeholder is refused. If you cannot write that sentence honestly, the change probably does
affect the spec, and this section is the wrong exit.
Checklist
- Searched for an existing document before creating one.
- Body is the FULL document, not a fragment.
-
## Purpose,### Requirement:with SHALL/MUST,#### Scenario:with GIVEN/WHEN/THEN. -
validate_specrun on the body; no new issues. -
propose_doc_deltawithsource_node_slugand a realreason. - In a
spec_firstproject: the delta came backdraft, and the user was not told it awaits their approval. - Draft worked out with
update_doc_delta(full body each time), rejected decisions written down as[decision]comments. -
submit_doc_deltacalled deliberately — that is the moment the question reaches a human. -
opsin the response matches what you meant to change. - If
require_proposal_approval: told the user, named the delta, and stopped. - Applied only after
review_status: approved, together with closing the work.