Skip to content

Commit 2ff53c5

Browse files
authored
Merge pull request #64 from ActiveMemory/feature/ctx-hub-next
refactor: flagbind batch helpers and convention sweep
2 parents ed8158b + 970cdd0 commit 2ff53c5

565 files changed

Lines changed: 144813 additions & 289796 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.context/CONSTITUTION.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,56 @@ DO NOT UPDATE FOR:
1414

1515
These rules are INVIOLABLE. If a task requires violating these, the task is wrong.
1616

17+
## Completion Over Motion
18+
19+
Work is only complete when it is **fully done**, not when progress has been made.
20+
21+
- The requested outcome must be delivered end-to-end.
22+
- Partial progress is not completion.
23+
- No half measures.
24+
25+
Do not:
26+
- Leave broken or inconsistent states
27+
- Deliver work that requires the user to "finish it later"
28+
29+
If you start something, you own it, you finish it.
30+
31+
---
32+
33+
## No Excuse Generation
34+
35+
**Never default to deferral.**
36+
37+
Your goal is to satisfy the user's intent, not to complete a narrow
38+
interpretation of the task.
39+
40+
Do not justify incomplete work with statements like:
41+
42+
- "Let's continue this later"
43+
- "This is out of scope"
44+
- "I can create a follow-up task"
45+
- "This will take too long"
46+
- "Another system caused this"
47+
- "This part is not mine"
48+
- "We are running out of context window"
49+
50+
Constraints may exist, but they do not excuse incomplete delivery.
51+
52+
- External systems, prior code, or other agents are not valid excuses
53+
- Inconsistencies must be resolved, not explained away
54+
55+
---
56+
57+
## No Broken Windows
58+
59+
Leave the system in a better state than you found it.
60+
61+
- Fix obvious issues when encountered
62+
- Do not introduce temporary hacks without resolving them
63+
- Do not normalize degraded quality
64+
65+
---
66+
1767
## Security Invariants
1868

1969
- [ ] Never commit secrets, tokens, API keys, or credentials

.context/DECISIONS.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<!-- INDEX:START -->
44
| Date | Decision |
55
|----|--------|
6+
| 2026-04-11 | Journal stays local; LEARNINGS.md is the shareable layer |
67
| 2026-04-09 | Architecture skill pipeline is a triad not a quartet |
78
| 2026-04-08 | Remove #done tag convention, simplify task archival |
89
| 2026-04-06 | Use hook relay for session provenance instead of JSONL parsing or env vars |
@@ -119,6 +120,178 @@ For significant decisions:
119120
120121
-->
121122

123+
## [2026-04-11-200000] Journal stays local; LEARNINGS.md is the shareable layer
124+
125+
**Status**: Accepted
126+
127+
**Context**: With the hub now carrying shared project context between machines
128+
and eventually between teammates, the question came up whether enriched
129+
journal entries should ride along — either the raw `.context/journal/` files
130+
or an "export enriched entries as shareable learning items" pipeline layered
131+
on top of `/ctx-journal-enrich`. The journal is already gitignored per the
132+
2026-03-05 `.context/memory/` decision and for the same reason: it's a
133+
first-person log of raw prompts, half-formed thoughts, dead ends, personal
134+
names, and things the user talks through with themselves. It sits in the
135+
same trust tier as shell history or a private notebook.
136+
137+
The trade-off is real: shared journals would make it trivial for teammates
138+
(or future-me on another machine) to see the full reasoning trail behind a
139+
decision. But "full reasoning trail" is precisely the thing that makes a
140+
journal journal and not a changelog — it includes the parts the author
141+
hasn't decided to stand behind yet, plus incidental private content.
142+
143+
**Decision**: The journal is **Tier-0 personal** and never leaves the
144+
originating machine. No hub sync, no export-by-default, no
145+
enriched-entries-as-shareable-items pipeline. The enrichment pipeline
146+
(`/ctx-journal-enrich`) stays as-is: journal → human-in-the-loop review →
147+
explicit promotion to LEARNINGS.md / DECISIONS.md / CONVENTIONS.md via the
148+
existing `/ctx-learning-add`, `/ctx-decision-add`, `/ctx-convention-add`
149+
commands. Those distilled artifacts are **Tier-1 shareable** and are what
150+
the hub syncs when a team opts into shared context.
151+
152+
The promotion boundary is therefore the enrichment step, not a new export
153+
pipeline. The user is the gate.
154+
155+
**Rationale**: Any "shareable enriched journal entry" pipeline would have to
156+
re-implement the trust boundary that `/ctx-learning-add` already enforces:
157+
the human decides what's worth sharing, strips incidental private content,
158+
and rewrites it as a standalone artifact. A second pipeline that tries to
159+
do this automatically would either (a) leak private content by accident, or
160+
(b) require the same human review and thus collapse back into
161+
`/ctx-learning-add`. The principled answer is that there is no second
162+
pipeline — LEARNINGS.md *is* the shareable form of the journal.
163+
164+
This also preserves the psychological safety of the journal: the author
165+
can write freely because they know nothing they write is one sync away
166+
from a teammate's screen. Lose that property and the journal stops being a
167+
journal and starts being a changelog draft.
168+
169+
**Consequence**:
170+
171+
- Journal files stay gitignored and stay out of `ctx hub` sync paths. Any
172+
future code that walks context files for replication must exclude
173+
`.context/journal/` explicitly and be covered by a test.
174+
- `/ctx-journal-enrich` remains the promotion boundary. Its output targets
175+
are LEARNINGS.md / DECISIONS.md / CONVENTIONS.md, never a separate
176+
"shareable journal" bucket.
177+
- Hub docs (`docs/home/hub.md`, `docs/recipes/hub-personal.md`,
178+
`docs/recipes/hub-team.md`, `docs/security/hub.md`) should state the
179+
Tier-0 / Tier-1 split explicitly so users building team workflows don't
180+
assume "shared context" means "shared everything."
181+
- The sync code path in `internal/hub/sync_helper.go` and any future
182+
replication of context files must enforce this exclusion at the
183+
code level — a gitignore entry is a user-convenience signal, not a
184+
hub-trust boundary.
185+
- A potential future "personal multi-machine journal sync" (same human,
186+
different laptops) is explicitly **out of scope** of this decision. If
187+
it ever ships, it rides a different transport (encrypted-at-rest,
188+
single-user, not the team hub) and needs its own decision record.
189+
190+
**Alternatives considered**:
191+
192+
- **Sync raw journal files via hub**: rejected. Inverts the gitignore
193+
decision, leaks private content by construction, destroys the
194+
journal's "safe to write freely" property.
195+
- **Auto-export enriched entries as a new shareable artifact type**:
196+
rejected. Duplicates `/ctx-learning-add` without the human gate, or
197+
collapses back into it. No real difference from the status quo except
198+
the opportunity for accidental leakage.
199+
- **Opt-in per-entry "publish to hub" flag in the journal**: rejected as
200+
premature. If the user wants an entry on the hub, the existing flow is
201+
one command away — write it as a learning or decision. A second path
202+
adds surface area without adding capability.
203+
204+
**Related**: Reinforces the 2026-03-05 `.context/memory/` gitignore
205+
decision (same trust-tier reasoning for a different private artifact).
206+
207+
## [2026-04-11-180000] `Entry.Author` is server-authoritative, not client-authoritative
208+
209+
**Status**: Accepted
210+
211+
**Context**: The `Entry.Author` field on hub entries is copied verbatim from
212+
the client's publish request (`handler.go:82`). It's optional, freeform, and
213+
unauthenticated — a client with a valid token for project `alpha` can publish
214+
entries claiming `Author: "bob@acme.com"` regardless of who actually
215+
authenticated. This is the same spoofing pattern as `Origin` (audit finding
216+
H-04) and was flagged as audit finding H-22 with three options: keep, drop,
217+
override, or promote. The decision was never formally closed.
218+
219+
The premise that resolved it: **identity is eventually part of the token**.
220+
Under the sysadmin-registry MVP, the server already knows `{user_id, project}`
221+
from the authenticated token. Under the PKI stretch, the signed claim carries
222+
identity cryptographically. In both models, the client has nothing to say about
223+
authorship that the server doesn't already know with higher confidence.
224+
225+
**Decision**: `Entry.Author` is **server-authoritative**. The server stamps it
226+
from the authenticated identity source on every publish. The client's
227+
`pe.Author` input is ignored (or rejected — implementation choice, not
228+
semantic difference). The field stays in the wire format but its semantics
229+
change from "whatever the client said" to "whatever the server's auth layer
230+
resolved."
231+
232+
Stamping source by phase:
233+
234+
- **Today (pre-registry)**: `Author = ClientInfo.ProjectName`, same source as
235+
the `Origin` server-enforcement fix (H-04). Lossy but consistent.
236+
- **Registry MVP**: `Author = users.json` row's `user_id` (e.g.,
237+
`alice@acme.com`). Precise per-human attribution.
238+
- **PKI stretch**: `Author = signed claim's sub field`. Cryptographic identity.
239+
240+
**Rationale**: Dropping the field is wrong because the registry MVP will
241+
already give us a per-user identity to stamp — removing Author just to re-add
242+
it later is churn. "Override" and "promote" are cosmetically different forms
243+
of the same decision (server fills from auth context); "promote" is what
244+
happens naturally once the registry MVP types the field as `UserID`.
245+
Client-sourced Author is indefensible because it replicates the Origin
246+
spoofing vector in a second field.
247+
248+
**Consequence**:
249+
250+
- The Author field stays on the wire and in `Entry{}`.
251+
- Client-side code that populates `pe.Author` from local config becomes a
252+
no-op. Audit `ctx connect publish` and `ctx add --share` for any such
253+
code paths before the server-enforcement fix lands.
254+
- `handler.go publish()` fills Author from the authenticated context (the
255+
same `ClientInfo` that H-04 pulls for Origin). Single unified
256+
auth-to-handler pipe.
257+
- `docs/security/hub.md` "Compromised client token" section gets rewritten:
258+
attribution becomes **wrong** on compromise (attacker's token maps to
259+
attacker's identity), not **forgeable** (attacker cannot stamp someone
260+
else's name).
261+
- The sysadmin-registry spec (`specs/hub-identity-registry.md`, tasked)
262+
MUST include a `user_id` field per row — it's the stamping source.
263+
- Three open tasks collapse into one: H-22 resolves to "implement
264+
server-authoritative Author" instead of "decide Author fate." TASKS.md
265+
updated.
266+
267+
**Alternatives considered**:
268+
269+
- **Keep client-authoritative**: rejected. Same spoofing vector as Origin;
270+
trivially defeats any downstream attribution check.
271+
- **Drop the field**: rejected. The registry MVP will need per-human
272+
attribution anyway. Dropping today is churn that gets undone
273+
immediately.
274+
- **Override at client-side before publish**: rejected. Puts the security
275+
boundary on the wrong side of the trust zone. Must be server-side.
276+
277+
**Follow-up — client-advisory metadata**: the client still has useful
278+
information to share that isn't an identity claim: a human-friendly
279+
display name, the machine that made the publish, the tool version, a
280+
CI system label, a team/role handle. This lives on a **new sibling
281+
field `Meta`** (a `ClientMetadata` sub-struct), not on `Author`. The
282+
separation of types is what protects the security property: `Author`
283+
is reserved for server-authoritative identity, `Meta` is
284+
client-advisory and explicitly labeled as such in any rendered
285+
surface. `Meta` fields are size-capped individually (256 bytes) and
286+
in aggregate (2 KB), validated for plain-string content (no
287+
newlines, no control characters), and never claimed as attribution
288+
in any API response. The renderer MUST label `Meta`-sourced values
289+
with prose like "client label" or "client-reported" so readers
290+
cannot mistake them for authoritative identity. See TASKS.md for
291+
the implementation task.
292+
293+
---
294+
122295
## [2026-04-09-001332] Architecture skill pipeline is a triad not a quartet
123296

124297
**Status**: Accepted

0 commit comments

Comments
 (0)