Darkwing's design note and SQLite prototype as records. Sage accepts
seven of eight open questions; the PM launching sessions goes to Jason.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`policy.change`, `prd.approve`); the validator refuses it.
- Routine work (editing files, running tests, writing docs, retrying) has
no outside effect, so it isn't in the vocabulary and needs no entry.
-`credentials` lists services and scopes, never a value or a path. The
business file binds each to a reference (section 2).
- There's no holder count. One instance has one holder (1.3). Two coders
are two instances of the same definition.
The four definitions slice 1 needs (decision 43):
| Role | withinRole | crossRole | Contract in short |
|---|---|---|---|
| `pm` | `task.create`, `task.assign`, `task.schedule`, `task.close`, `role.launch` (if Jason allows it, see 6.6), `message.send` | `task.priority.change` against the CTO's call | Breaks requirements into tasks; every task cites a PRD requirement; arbiter for delivery order |
| `session.launched` | the stack starts an agent | role, harness, model, vars digest and provenance, credential service names, contract digest |
| `session.ended` | it stops | exit code, reason |
| `message.sent`, `.routed`, `.delivered`, `.failed`, `.read` | `messages` and `deliveries` rows | class, to_role, transport |
| `decision.raised`, `.seen`, `.resolved`, `.withdrawn` | `decisions` and `decision_events` rows | class, action, route_to, choice, time open |
| `action.allowed` | a within-role action is done | action, target |
| `action.refused` | a verb or hook refuses | action, class, layer that refused (credential, verb, hook) |
| `task.created`, `.assigned`, `.state`, `.closed` | the adapter writes to the tracker | tracker ref, requirement ref, role |
| `task.changed.external` | a person changed a task in Vikunja | tracker ref, fields |
| `task.conflict` | the tracker and the queue disagree | both values |
| `review.requested`, `review.verdict` | mirrored from the queue log | row, round, verdict |
| `human.input` | Jason does anything through `mosaic` | kind: `answer` (a decision), `instruction` (a message to a role), `admin` (launch, relaunch, credential, status) |
| `config.refused` | a layer or role file fails validation | file, reason code |
`human.input` is the event that answers Jason's "reduced in what way?".
Answers are decisions, while instructions and admin are what slice 1
should push toward zero, with no hand tagging. The CLI knows which verb
ran, so it writes the kind. Talking to a role in free text counts as
`instruction`.
## 5. Tasks and Vikunja
Things I took from memory and haven't checked; Researcher's report
should confirm or correct them:
- Vikunja's REST API is under `/api/v1`.
- It has API tokens with permissions per route group, and webhooks per
project.
- Tasks have assignees (users), labels, a due date, a priority, a `done`
flag and kanban buckets.
- A task's `updated` timestamp is the only concurrency signal; I don't
tryit("UPDATE decisions",()=>db.exec("UPDATE decisions SET recommendation='A' WHERE id='d-1'"));
tryit("DELETE decision_events",()=>db.exec("DELETE FROM decision_events"));
tryit("DELETE role_claims",()=>db.exec("DELETE FROM role_claims"));
tryit("INSERT OR REPLACE decisions",()=>db.exec(`INSERT OR REPLACE INTO decisions (id,at,business,raised_by_role,raised_by_run,class,action,route_to,question,options,recommendation) VALUES ('d-1','x','b','r','r','gated','a','human','q','${opts}','A')`));
tryit("UPSERT decisions",()=>db.exec(`INSERT INTO decisions (id,at,business,raised_by_role,raised_by_run,class,action,route_to,question,options,recommendation) VALUES ('d-1','x','b','r','r','gated','a','human','q','${opts}','A') ON CONFLICT(id) DO UPDATE SET recommendation='A'`));
console.log("rows:",db.prepare("SELECT count(*) n FROM decisions").get().n,"rec:",db.prepare("SELECT recommendation r FROM decisions WHERE id='d-1'").get().r);
console.log("journal:",db.prepare("PRAGMA journal_mode").get().journal_mode,"| triggers:",db.prepare("SELECT count(*) n FROM sqlite_master WHERE type='trigger'").get().n);
constbase=`CREATE TABLE decisions (seq INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, rec TEXT NOT NULL) STRICT;
CREATE TRIGGER d_no_update BEFORE UPDATE ON decisions BEGIN SELECT RAISE(ABORT,'append-only'); END;
CREATE TRIGGER d_no_delete BEFORE DELETE ON decisions BEGIN SELECT RAISE(ABORT,'append-only'); END;
INSERT INTO decisions (id,rec) VALUES ('d-1','B');`;
constguard=`CREATE TRIGGER d_no_replace BEFORE INSERT ON decisions WHEN EXISTS (SELECT 1 FROM decisions WHERE seq = NEW.seq OR id = NEW.id) BEGIN SELECT RAISE(ABORT,'append-only: key exists'); END;`;
BEGINSELECTRAISE(ABORT,'revoke needs a resolved decision');END;
CREATETRIGGERmeta_no_updateBEFOREUPDATEONmetaBEGINSELECTRAISE(ABORT,'meta is append-only');END;
CREATETRIGGERmeta_no_deleteBEFOREDELETEONmetaBEGINSELECTRAISE(ABORT,'meta is append-only');END;
CREATETRIGGERevents_no_updateBEFOREUPDATEONeventsBEGINSELECTRAISE(ABORT,'events is append-only');END;
CREATETRIGGERevents_no_deleteBEFOREDELETEONeventsBEGINSELECTRAISE(ABORT,'events is append-only');END;
CREATETRIGGERrole_claims_no_updateBEFOREUPDATEONrole_claimsBEGINSELECTRAISE(ABORT,'role_claims is append-only');END;
CREATETRIGGERrole_claims_no_deleteBEFOREDELETEONrole_claimsBEGINSELECTRAISE(ABORT,'role_claims is append-only');END;
CREATETRIGGERdecisions_no_updateBEFOREUPDATEONdecisionsBEGINSELECTRAISE(ABORT,'decisions is append-only');END;
CREATETRIGGERdecisions_no_deleteBEFOREDELETEONdecisionsBEGINSELECTRAISE(ABORT,'decisions is append-only');END;
CREATETRIGGERdecision_events_no_updateBEFOREUPDATEONdecision_eventsBEGINSELECTRAISE(ABORT,'decision_events is append-only');END;
CREATETRIGGERdecision_events_no_deleteBEFOREDELETEONdecision_eventsBEGINSELECTRAISE(ABORT,'decision_events is append-only');END;
CREATETRIGGERmessages_no_updateBEFOREUPDATEONmessagesBEGINSELECTRAISE(ABORT,'messages is append-only');END;
CREATETRIGGERmessages_no_deleteBEFOREDELETEONmessagesBEGINSELECTRAISE(ABORT,'messages is append-only');END;
CREATETRIGGERdeliveries_no_updateBEFOREUPDATEONdeliveriesBEGINSELECTRAISE(ABORT,'deliveries is append-only');END;
CREATETRIGGERdeliveries_no_deleteBEFOREDELETEONdeliveriesBEGINSELECTRAISE(ABORT,'deliveries is append-only');END;
CREATETRIGGERmeta_no_replaceBEFOREINSERTONmetaWHENEXISTS(SELECT1FROMmetaWHEREkey=NEW.key)BEGINSELECTRAISE(ABORT,'meta is append-only');END;
CREATETRIGGERevents_no_replaceBEFOREINSERTONeventsWHENEXISTS(SELECT1FROMeventsWHEREseq=NEW.seqORid=NEW.id)BEGINSELECTRAISE(ABORT,'events is append-only');END;
CREATETRIGGERrole_claims_no_replaceBEFOREINSERTONrole_claimsWHENEXISTS(SELECT1FROMrole_claimsWHEREseq=NEW.seq)BEGINSELECTRAISE(ABORT,'role_claims is append-only');END;
CREATETRIGGERdecisions_no_replaceBEFOREINSERTONdecisionsWHENEXISTS(SELECT1FROMdecisionsWHEREseq=NEW.seqORid=NEW.id)BEGINSELECTRAISE(ABORT,'decisions is append-only');END;
CREATETRIGGERdecision_events_no_replaceBEFOREINSERTONdecision_eventsWHENEXISTS(SELECT1FROMdecision_eventsWHEREseq=NEW.seq)BEGINSELECTRAISE(ABORT,'decision_events is append-only');END;
CREATETRIGGERmessages_no_replaceBEFOREINSERTONmessagesWHENEXISTS(SELECT1FROMmessagesWHEREseq=NEW.seqORid=NEW.id)BEGINSELECTRAISE(ABORT,'messages is append-only');END;
CREATETRIGGERdeliveries_no_replaceBEFOREINSERTONdeliveriesWHENEXISTS(SELECT1FROMdeliveriesWHEREseq=NEW.seq)BEGINSELECTRAISE(ABORT,'deliveries is append-only');END;
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.