Skip to content

feat(spec): add kind: edit, for tasks that already exist - #624

Open
holmboe wants to merge 1 commit into
spec-interpolatefrom
spec-kind-edit
Open

holmboe wants to merge 1 commit into
spec-interpolatefrom
spec-kind-edit

Conversation

@holmboe

@holmboe holmboe commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Second of three stacked pull requests. Based on #623; review only the last commit, and retarget to main once that one is merged.

A create spec can never touch an existing object, by design. This adds the counterpart, kind: edit, for tasks:

spec: phorge/v1alpha1
kind: edit
tasks:
  - task: T123
    status: resolved
    subscribers:
      - +sebastian.soderberg

phabfive apply -f compares each item with the task as the server has it and sends only the difference, through the engine phabfive edit already uses. No new transaction-building code and no new Conduit method.

  • A key that is absent is left alone.
  • A list is the full set, or a delta of +name and -name entries. A mix is refused (mixed-list). Nothing is sent as a set.
  • A value is a state, not a move. priority: raise and column: forward are refused (relative-value).
  • Applying twice is applying once. A task that already matches is reported unchanged, exit 0.
  • modified: is an optional ISO 8601 stamp. An item that would change a task modified after its stamp is refused as stale, exit 2; apply --force overrides.
  • Every item is planned before any is applied, and each problem is reported under its own key. phabfive spec validate runs the same plan as its online layer.
  • kind: edit is never inferred. A create item carrying task: or modified: is refused by name, so an edit spec that lost its kind cannot create a duplicate.
  • phabfive apply -f - and phabfive spec validate - read the spec from standard input.

docs/edit-specs.md is the guide, docs/phorge-spec.md gains "Edit documents", and specs/edit/close-out.yaml is a runnable example.

Behaviour worth a look in review

  • The stamp is consulted only when the item would change something, and only refuses when the server is newer. Otherwise a rerun after a successful or partial apply would be refused for the change the first run made. The stamp is to the second, so a change within the same second is not caught.
  • column: names no board, so it means the one workboard the task is on once the item is applied. An item that says both column: and a different projects: moves the card on the new board, or is refused as unknown-column when that board has no such column.
  • No confirmation prompt, as for a create spec, although phabfive edit confirms title, description and policy changes. --dry-run is the preview.
  • Project and package subscribers are kept as they are; a spec subscribes and unsubscribes users only.
  • The assignee key is assignment, as in a create spec, so the two write kinds share their keys.

Test plan

  • Unit: the vocabulary and offline rules (tests/test_spec_edit_offline.py), the planner against a fake engine (tests/test_edit_spec.py), and the command (tests/test_cli_apply_edit.py), including the deprecated maniphest create --with refusing an edit spec.
  • tests/e2e/test_edit_spec.py against a local Phorge stable: the difference is sent once, a rerun is a no-op, a stale item is refused and --force applies it, every problem is reported and nothing is written, and stdin.
  • make test-e2e on stable: 160 passed, 2 skipped.
  • Full unit suite, ruff, ruff format, mypy, scripts/gen_spec_docs.py --check and mkdocs build --strict.
  • phabricator-stable was not run locally; the ci:phabricator-stable label ran it here, and it passes. The support matrix is unchanged, since no new Conduit method, constraint or field is used.

🤖 Generated with Claude Code

A create spec can never touch an existing object, by design. An edit spec
is the counterpart: each item names its task with `task: T123` and states
what it should be, and `phabfive apply -f` sends only the difference
through the engine `phabfive edit` uses.

- A key that is absent is left alone, so `{task: T123, status: resolved}`
  is a whole edit
- A list is the full set, or a delta of `+name` and `-name` entries; a mix
  is refused. Nothing is ever sent as a set
- Relative values (`priority: raise`, `column: forward`) are refused: a
  file applied twice has to name where the task ends up
- A task that already matches is left alone, so applying a file twice
  changes nothing the second time
- An item carrying `modified:` is refused as `stale` when the task was
  modified after it and the item would change it; `apply --force`
  overrides. The stamp is consulted only when there is something to send,
  so a rerun is not refused for the change the first run made
- Every item is planned before any is applied, and each problem is
  reported under its own key
- `kind: edit` is never inferred, and a create item carrying `task:` or
  `modified:` is refused by name, so an edit that lost its kind cannot
  create a duplicate

`phabfive apply -f -` and `phabfive spec validate -` read the spec from
standard input.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@holmboe holmboe added ci:phorge-stable Test Phorge stable in the Kubernetes deploy check ci:phabricator-stable Test Phabricator stable, as Phacility left it, in the Kubernetes deploy check labels Nov 12, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:phabricator-stable Test Phabricator stable, as Phacility left it, in the Kubernetes deploy check ci:phorge-stable Test Phorge stable in the Kubernetes deploy check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant