Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
426 changes: 426 additions & 0 deletions docs/architecture-report.md

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions docs/diagrams/auto-merge-veto.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@startuml auto-merge-veto
title Symphony auto-merge - 24 h veto window (policy: auto-with-veto)

skinparam shadowing false
skinparam defaultFontName Helvetica

start
:QA stage transitions ticket
to ready-to-merge;
:Symphony tick finds ticket;
if (veto deadline comment exists\non the Kaneo ticket?) then (no)
:post to ops Telegram topic -
"PR #N ready to auto-merge in 24 h.
Reply /reject TASK-ID to cancel";
:write "Window closes at <iso>"
comment on the ticket
(durable across Symphony restarts);
else (yes)
:parse deadline from comment;
endif
repeat
:next Symphony tick (30 s);
if (/reject received?\n(cancel label on ticket)) then (yes)
:ticket -> review (cancelled);
stop
endif
repeat while (deadline passed?) is (no)
-> yes;
:gh pr merge --squash --delete-branch;
:ticket -> done;
:last-known-good tag advanced
(non-loop tasks; safe-mode hook);
stop
@enduml
1 change: 1 addition & 0 deletions docs/diagrams/auto-merge-veto.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 75 additions & 0 deletions docs/diagrams/content-publish-process.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@startuml content-publish-process
title content-publisher - end-to-end publish process

skinparam shadowing false
skinparam defaultFontName Helvetica

|Telegram bot|
start
:operator posts brief in
blogger-prompts topic;
:append job to queue.jsonl
(flock, append-only);

|content-publisher worker|
:queue-poll loop picks job (~5 s);
:CAS queued -> writing;
:spawn claude --skill blog-writer
(prompt via stdin, allowlisted env);
:article.md written to worktree;
:CAS writing -> scoring;
:run analyze_blog.py subprocess;
if (score >= MNEMONIK_MIN_SCORE (80)?) then (yes)
:CAS scoring -> preview-sent;
else (no)
:CAS scoring -> failed;
stop
endif

|Telegram bot|
:send byte-identical preview
with Publish / Reject / Regenerate
buttons (HMAC-signed callbacks);
if (operator decision\n(or approval deadline)) then (publish / timeout / auto)
:CAS preview-sent -> publishing;
elseif (reject) then
:CAS preview-sent -> rejected;
stop
else (regenerate)
:CAS preview-sent -> regenerated;
:new job appended to queue;
stop
endif

|content-publisher worker|
:rate ceiling check (20 posts/h);
:write tentative_publish_started_at
forensic marker;
:mnemonik_blogger.run_campaign_from_article
-> posts to @mnemonik channel;
if (publish ok?) then (yes)
:CAS publishing -> published
(post_url, message_ids, content_sha256);
else (no)
:CAS publishing -> publish-failed;
stop
endif
:spawn mnemonik-mcp mcp-stdio;
:JSON-RPC initialize +
tools/call mnemonic_sign_memory;
if (attestation ok?) then (yes)
:CAS published -> done
(attestation_hash recorded);
else (no)
:CAS published -> attest-pending;
repeat
:retry attestation (every 5 min);
repeat while (still failing and < 24 h?) is (yes)
-> 24 h escalation window elapsed;
:CAS attest-pending -> attest-failed;
stop
endif
:cleanup-gc after 24 h -
rmtree worktree, archive job line;
stop
@enduml
1 change: 1 addition & 0 deletions docs/diagrams/content-publish-process.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions docs/diagrams/content-publisher-job-states.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@startuml content-publisher-job-states
title content-publisher - job state machine (13 states, models.py allowed_transitions)

skinparam shadowing false
skinparam defaultFontName Helvetica

state "queued" as queued
state "writing" as writing
state "scoring" as scoring
state "preview-sent" as preview_sent
state "publishing" as publishing
state "published" as published
state "attest-pending" as attest_pending
state "done" as done
state "rejected" as rejected
state "regenerated" as regenerated
state "publish-failed" as publish_failed
state "attest-failed" as attest_failed
state "failed" as failed

[*] --> queued : bot appends job\nto queue.jsonl

queued --> writing : worker picks job\n(CAS, flock)
queued --> failed : unrecoverable error
writing --> scoring : article.md produced
writing --> failed : claude spawn error
scoring --> preview_sent : score >= 80
scoring --> failed : score gate / error
preview_sent --> publishing : operator Publish,\napproval deadline,\nor auto mode
preview_sent --> rejected : operator Reject
preview_sent --> regenerated : operator Regenerate\n(new job appended)
publishing --> published : posted to @mnemonik
publishing --> publish_failed : publish error
published --> done : mnemonic_sign_memory ok\n(first try)
published --> attest_pending : attestation failed
attest_pending --> done : retry ok (every 5 min)
attest_pending --> attest_failed : 24 h escalation\nwindow elapsed

writing -[dashed]-> queued : boot recovery\n(recovery.py)
scoring -[dashed]-> queued : boot recovery\n(recovery.py)

done --> [*] : cleanup-gc after 24 h\n(archive + rmtree)
rejected --> [*]
regenerated --> [*]
publish_failed --> [*]
attest_failed --> [*]
failed --> [*]

note bottom of attest_pending
All transitions go through cas_status():
flock(.lock) + read-modify-write +
os.replace, checked against this graph
(StaleStateError on mismatch)
end note
@enduml
1 change: 1 addition & 0 deletions docs/diagrams/content-publisher-job-states.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading