Skip to content

feat(issue-tracker): surface topology — contested seams as first-class board objects - #54

Merged
SSFSKIM merged 14 commits into
mainfrom
worktree-surface-topology
Aug 10, 2026
Merged

feat(issue-tracker): surface topology — contested seams as first-class board objects#54
SSFSKIM merged 14 commits into
mainfrom
worktree-surface-topology

Conversation

@SSFSKIM

@SSFSKIM SSFSKIM commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Implements the approved spec docs/doperpowers/specs/2026-08-10-surface-topology-design.md (v2). ExecPlan with full history: docs/doperpowers/execplans/2026-08-10-surface-topology.md.

What

A consumer repo can declare contested code seams in .doperpowers/surfaces.md (read from the default branch, freshly fetched). The board then:

  • labels matching tickets surface:<name> at three moments — registration (identifiers + --surface hints, label rides the create call), lane-entry transition (re-match of the current body), and the sweep's new SURFACE pass (open linked PR diffs via REST pulls/N/files, renames included; add-only);
  • serializes dispatch — one in-flight implement worker per surface, enforced under per-surface mkdir locks held from the occupancy check through board-bind (occupancy = board states ∪ bound registry metas ∪ in-tick claims; architect occupies but is never blocked; spikes neither; epics excluded; SURFACE_OVERRIDE=1 bypasses loudly);
  • auto-relates label-mates (register-time under the same locks, sweep backstop with per-side repair);
  • auto-registers a consolidation ticket (ready-for-architect) when ≥3 members pile onto one surface (parks count, deferred doesn't) — structural dedupe via the label across the ticket's whole open lifecycle;
  • lints the closed vocabulary (invented surface:* = FAIL, FIX names the new board-surface.sh verb), registry names, declaration drift, and per-surface queue depth.

Opt-in inertness is a hard requirement: no surfaces.md → zero behavior change (leftover labels included), guarded by tests.

Verification

  • tests/issue-tracker/test-board-surface.sh — new, 38 assertions (spec acceptance 1–9)
  • tests/implementing/test-implement-dispatch.sh — +14 surface assertions
  • tests/issue-tracker/test-board-scripts.sh, test-board-sweep.sh, tests/claude-code/board-api/test-register-transition.sh — pass unchanged
  • scripts/lint-shell.sh clean on the changed set
  • Two codex whole-branch review rounds (gpt-5.6-sol): 13 findings, all adopted (lock-through-bind, register relate locking, --slurp pagination, registry fetch, lifecycle dedupe, registry gating, rename matching, per-side relates repair, stderr notices, contention logging — commits 96e98d10, 6e484dda)

Out of scope (follow-ups)

SSFSKIM added 12 commits August 10, 2026 19:48
…gate, lifecycle dedupe, rename matching

All 8 findings adopted:
- per-surface mkdir locks (DAEMON_HOME/surface-locks/) held from the
  occupancy check through the spawn; the sweep's relates RMWs take the
  same lock — one mechanism closes both the cross-process double-dispatch
  window and the relates-vs-dispatch TOCTOU [P1 x2]
- occupancy re-checked UNDER the lock (fresh snapshot helper), not
  exported from the pre-lock ticket read
- surfaces_registry() best-effort-fetches the default branch first — a
  remotely merged registry entry reaches long-lived clones [P1]
- consolidation dedupe covers the ticket's whole open lifecycle: arch
  states before decompose, epic-with-children after [P1]
- no registry → surface labels ignored by dispatch too (T_SURFACES
  gated) — leftover labels cannot queue work forever [P2]
- consolidation label rides the register call via --surface (atomic
  with create; no window without the dedupe key) [P2]
- PR files read via REST pulls/N/files with previous_filename — renames
  out of a surface still label [P2]
- relates edges checked and repaired per SIDE — one-sided edges from a
  crashed tick converge [P2]
- plus the #52 finding-5 code twin: queue-depth members count parks
  (only deferred/terminal/spike/epic/architect are out)
…elate locking, paginated diffs

- the surface lock now survives until board-bind writes the ticket field
  (a spawn-time meta is unbound — the registry arm cannot see it, so the
  spawn-time release reopened the cross-process window) [P1]
- register-time relate RMWs take the same per-surface locks and re-check
  liveness under them (issue-event dispatch can bind a worker to the new
  ticket within seconds); contention defers the edge to the sweep [P1]
- PR file reads use --paginate --slurp with page flattening — a
  multi-page diff was concatenated JSON arrays that json.loads rejected,
  silently skipping the PR's labels (sweep + lint) [P1]
- the --surface no-registry notices go to stderr, keeping the
  '<number> <url>' first-line stdout contract [P2]
- lock contention under SURFACE_OVERRIDE=1 (or the architect lane) logs
  the unlocked bypass explicitly [P2]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d03db92ae7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if [ -n "${T_SURFACES:-}" ] && [ "$role" != "SPIKE" ]; then
if _surf_lock "$T_SURFACES"; then
surf_locked="$T_SURFACES"
occ="$(_surface_occupant "$n" "$T_SURFACES" "${DISPATCHED_SURFACES:-}")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fail closed when the occupancy lookup errors

When the under-lock snapshot fails because of a transient GitHub/API error, this command's exit status is unchecked. In sweep mode dispatch_one is called behind ||, so errexit is disabled and occ is treated as empty, allowing a worker to spawn without proving the surface is free; in triggered mode the script exits while leaving the surface lock behind until its stale timeout. Handle the nonzero status explicitly, release the lock, and leave the ticket queued.

Useful? React with 👍 / 👎.

Comment thread skills/issue-tracker/scripts/_board.py Outdated
Comment on lines +517 to +521
if not os.environ.get("SURFACES_REF") and "/" in ref:
remote, _, branch = ref.partition("/")
try:
subprocess.run(["git", "fetch", "--quiet", remote, branch],
capture_output=True, timeout=30, check=False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid fetching the default branch for every ticket

In a registry-enabled repo this unconditional fetch runs once per Python process, while the cache below is only process-local. A dispatch sweep starts fresh Python processes for each ticket's _ticket_exports and again for each _surface_occupant, so a board with N eligible surface tickets can perform roughly 2N fetches; a slow or unreachable remote can consume the full 30-second timeout each time and stall a nominal five-minute sweep for many minutes. Refresh once per outer tick/process group, or gate refreshes with a shared TTL.

Useful? React with 👍 / 👎.

Comment on lines +568 to +571
if c == "*":
if pat[i:i + 2] == "**":
out.append(".*")
i += 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let globstar cover zero nested directories

The documented ** path glob fails to match zero directory levels because the generated .* is surrounded by both literal slashes. For example, src/**/*.py matches src/pkg/a.py but not src/a.py, so a valid surface pattern silently misses files directly under the named directory and those tickets remain unlabeled and unserialized. Treat **/ as matching zero or more complete path segments.

Useful? React with 👍 / 👎.

# architect lane (the resolver) are out.
ARCH_STATES = ("ready-for-architect", "in-design")
OUT_STATES = B.TERMINAL + ARCH_STATES + ("deferred",)
for s in sorted({x for n in tickets.values() for x in n["surfaces"]}):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Ignore orphaned surfaces in the consolidation pass

This loop considers every surface:* label present on tickets rather than restricting itself to names in reg. If three open tickets carry an invented label, or a registry entry is deleted before its carriers are cleared, the sweep emits CONSOLIDATE for that orphan; board-register --surface cannot apply the nonexistent surface label to the new architect ticket, so the structural dedupe never becomes visible and another consolidation issue is created on every tick. Filter queue-depth processing to registered surface names.

Useful? React with 👍 / 👎.

…fetch stamp, lane-scoped spike exemption

- SURFACE pass (relates + queue-depth) acts only on names present in the
  registry: an orphaned label reaching CONSOLIDATE registered with a
  --surface hint that matched nothing — an unlabeled consolidation the
  structural dedupe never saw, duplicated every tick [P1]
- surfaces_registry() fetch behind a cross-process stamp
  (SURFACES_FETCH_TTL, default 300s): a dispatch sweep runs a fresh
  python per ticket, so N labeled tickets meant N fetches per tick [P2]
- spike occupancy exemption is lane-scoped: a spike-category ticket in
  ready-for-architect/in-design routes ARCHITECT (state outranks
  category) and its design run occupies like any architect's [P2]
@SSFSKIM
SSFSKIM merged commit 1f4f87f into main Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant