Skip to content

fix(repositories): stop the shared-pointer live edit in MemoryGraphRepository (A-06) - #100

Open
gustavobertoi wants to merge 5 commits into
mainfrom
feat/a-06-graph-deep-copy
Open

gustavobertoi wants to merge 5 commits into
mainfrom
feat/a-06-graph-deep-copy

Conversation

@gustavobertoi

Copy link
Copy Markdown
Collaborator

Summary

BACKLOG_V2.md A-06 · Stop the shared-pointer live edit (= F-01 finding 3).

MemoryGraphRepository.FindByID (internal/repositories/graph_memory.go) returned the
exact *workflow.Graph pointer stored by Save — the same object
internal/actors/workflow_handler.go hands to a triggered Workflow. A later
GraphService.UpsertupdateVersionedGraph.UpdateSchema
(internal/services/graph_service.go:244-284, internal/workflow/graph.go:122-133) for the
same schema id then rewrote that in-flight run's topology in place: a forking node could
lose a branch mid-run, and Workflow.Next could nil-deref a node deleted out from under it
(internal/workflow/workflow.go:312-314).

The Postgres driver was already immune — postgres/graph.go:FindByID rebuilds a fresh
Graph from the object store on every call.

Fix

FindByID now returns a fresh *workflow.Graph built from a cloned Schema():

schema := graph.Schema()
return workflow.NewGraph(&schema)

This mirrors the existing FindByIDAndVersion idiom in the same file verbatim
(schema := sv.Schema.Clone(); workflow.NewGraph(&schema)), and needs no change to
updateVersioned, SetActiveVersion, or Rollback — the latter two already build fresh
Graph values from a cloned schema and are out of scope here (that territory belongs to
A-05). A grep of every GraphRepository.FindByID / GraphService.FindByID call site
(cron_scheduler.go, event_trigger.go, workflow_sup.go, webhook_router.go,
workflow_handler.go:168, workflow_schema.go, webhook.go, plus tests) confirmed none
assert pointer identity across calls, so the return-semantics change is safe.

GraphService.FindByID already handles the resulting !IsNodesMetadataPopulated() case by
repopulating (internal/services/graph_service.go:69-74) — the same cost the Postgres driver
already pays on every call, not a regression.

Proof — the F-01.3 tests go from failing to passing

These were checked in on main as part of F-01 verification and are this task's stage-1
evidence; not touched by this PR:

  • internal/services/f01_3_verification_test.go (3 unit tests)
  • tests/functional/f01_3_verification_test.go (memory contract)
  • tests/functional/f01_3_verification_pg_test.go (Postgres contract, //go:build functional)

All 4 confirmed red on this branch before the fix, green after.

Test plan

  • make swagger && make lint && make build && make test — lint 0 issues, build clean,
    make test 736/738 passing. The 2 remaining failures
    (TestWorkflowFunc_HandleMessage_GivesNodeFunctionTheNodeSpanContext,
    TestWorkflowHandler_ResumeWithPendingSubWorkflow_KeepsExactlyOneDistinctChild) are
    F-01 findings 1 and 2 (A-04 / A-02 territory) — confirmed pre-existing on this branch
    without this fix via git stash, unrelated to graph_memory.go, out of scope for
    A-06.
  • DB_POSTGRES_DSN=... make test-functional against a real Postgres 17 (make infra-up)
    — 140/140 passing, including PostgresGraphRepository live schema edit isolation contract — driver parity confirmed both ways.
  • go test -race -count=1 ./internal/repositories/... ./internal/services/... ./tests/functional/... — clean.

🤖 Generated with Claude Code

gustavobertoi and others added 5 commits August 11, 2026 21:30
…owledge packs

BACKLOG_V2 tasks are derived from a capability report about the code, not from
the code ("The report describes the code; it is not the code"), so an agent that
treats it as ground truth will confidently implement around bugs that do not
exist. This adds a pipeline that separates verification, planning, building and
review into distinct agents with explicit boundaries.

Agents under .agents/agents/ (per ADR-0009, .claude/agents symlinks into it):
premise-verifier, engine-researcher, implementation-planner, protocol-spec-author,
go-implementer, e2e-harness-engineer, code-reviewer.

Knowledge packs under .agents/skills/: backlog-task (the orchestrating pipeline,
with a routing table for every task id), plus durable-execution-internals,
crash-resume-testing, persistence-and-migrations, observability-tracing,
function-package-authoring, remote-node-protocol, capability-registry-and-agents.

Two boundaries are drawn explicitly because dispatch gets them wrong: process-death
tests belong to the harness engineer and involve no production Go, and the spec
author is the first call for B-01 or any wire-surface change.

Recorded as ADR-0034.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ed findings

One document per task at docs/backlog/<ID>.md — filenames are the bare id so the
pipeline can open a task by id, deliberately unlike docs/adr/'s NNNN-slug.md.
Each carries the backlog text verbatim, premise status distinguishing EXECUTED
from READ from UNVERIFIED, the code area, the agent route, "Done when" as
checkboxes, out-of-scope, the ADR/spec call, and a dated log.

45 documents: F x3, A x11, B x10, C x12, D x6, plus V-a/V-b/V-c. Tier B, Tier C
proposed work and all of Tier D are marked unmistakably as net-new — presenting a
proposal as shipped behaviour is the failure mode these files exist to prevent.

BACKLOG_V2.md gains a "Found during verification — unfiled" section with the three
bugs F-01 turned up, recorded rather than fixed per the backlog's own rule:
  V-a  agent tool calls bypass workflow_func.go, so they emit no node.execute span
       and no duration metric — evidence against B-03's node/tool parity requirement
  V-b  resume appends a second subworkflow:started for one exec id; no DB constraint
       catches it (sub_workflow_refs is UNIQUE on child_workflow_id only)
  V-c  Workflow.Next discards the FindNode error and derefs nil, crashing the actor
Ids and tiers are left unassigned on purpose — that is the maintainer's call.

BACKLOG.md is marked superseded rather than deleted; every id in it was renumbered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
F-01's deliverable: each finding is a checked-in test that fails on the current
tree. All three REPRODUCED. No production code is modified — the point is to
demonstrate the defects against the tree as it stands.

  .1  the node span context is discarded (workflow_func.go:96), so function code
      cannot inject traceparent. Scope correction: `_ = nodeCtx` is a symptom, not
      the defect — nothing downstream can accept a context (ExecuteFunction,
      FunctionTransport.Execute and type Function are all context-free), so A-04
      is a public pkg/ API change with design tension against ADR-0027.
  .2  a restart with a pending system/subworkflow spawns a second child. All four
      links verified. Scope correction: synchronous sub-workflows only — the async
      branch journals step:completed at workflow_handler.go:936 and is immune.
  .3  the memory graph repo returns the live pointer, so an Upsert rewrites a
      running run's topology. Driver parity executed against a real Postgres 17:
      the postgres driver is immune. This is also A-06's premise.

Each test asserts the DEFECT, not a fix: verified empirically by applying candidate
fixes and confirming each test flips green under every shape tried, and stays red
under a paper-over. An earlier revision asserted source text, a row count a correct
fix would not change, and clone-on-read specifically; all three were rewritten.

These stay on this branch. They cannot go green until A-04, A-02 and A-06 land, so
each merges as the first commit of the PR that fixes its defect rather than turning
main red for two sprints. See docs/backlog/F-01.md for the mapping.

Verified: build ok, lint 0 issues, go test -race reports no data race, and the only
reds in the tree are these six.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 45 per-task documents now live as draft items on
https://github.com/orgs/open-source-cloud/projects/2 — one per task, each carrying
its full dossier as the item body (premise status distinguishing EXECUTED from READ
from UNVERIFIED, code area with file:symbol, dependencies, agent route, done-when,
ADR notes, dated log).

Keeping both would have meant two sources of task state drifting apart on the first
edit. BACKLOG_V2.md remains the narrative and the rationale; the board owns status,
size, priority and assignment.

Board fields were derived from the backlog only:
  Status    Done = F-01 (verified and closed); Ready = Sprint 1 remainder
            (F-02, F-03, A-06, A-07); Backlog = everything else
  Size      the backlog's declared S/M/L; Tier D left unset (no declared size)
  Priority  P0 = Sprint 1 plus B-01 (gates all of Tier B, "before writing any Go")
            P1 = Sprint 2/3 remainder and the rest of Tier A; P2 = everything else

The documents remain in this branch's history at commit 4225bb4 if a file-based
copy is ever wanted again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ve graph pointer

BACKLOG_V2.md A-06 (F-01 finding 3). MemoryGraphRepository.FindByID returned the exact
*workflow.Graph stored by Save, which internal/actors/workflow_handler.go hands to a
triggered Workflow. A later GraphService.Upsert -> updateVersioned -> Graph.UpdateSchema
for the same schema id then rewrote that in-flight run's topology in place: a forking
node lost a branch mid-run, and Workflow.Next could nil-deref a node deleted from under
it (internal/workflow/workflow.go:312-314).

FindByID now returns a fresh Graph built from a cloned Schema, mirroring the existing
FindByIDAndVersion idiom on the same file and the Postgres driver, which already rebuilds
a Graph from the object store on every call. SetActiveVersion and Rollback already build
fresh Graph values and are unaffected (A-05's territory).

Turns the checked-in F-01.3 tests from failing to passing:
- internal/services/f01_3_verification_test.go (3 unit tests)
- tests/functional/f01_3_verification_test.go /  _pg_test.go (memory + postgres contract)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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