| 1 | # π€ fichas β design |
| 2 | |
| 3 | **Status:** draft, revision 1, 2026-09-19. **Nothing here is built.** No |
| 4 | repo, no module, no binary β this file and its directory are the whole of |
| 5 | fichas today. |
| 6 | |
| 7 | A *ficha* is an index card; a *fichero* is the card cabinet. |
| 8 | |
| 9 | --- |
| 10 | |
| 11 | ## 0. The one rule |
| 12 | |
| 13 | **Fichas is an app of record, and memoria is not.** Every decision below |
| 14 | falls out of that. memoria may index fichas, cite it and reach into it; |
| 15 | fichas owns its content, answers for it, and is the only thing that may |
| 16 | delete it. |
| 17 | |
| 18 | --- |
| 19 | |
| 20 | ## 1. Decisions, and who made them |
| 21 | |
| 22 | The suite's specs keep this table because a decision whose load-bearing |
| 23 | half lives only in a commit message gets relitigated. **The `Who` column is |
| 24 | load-bearing: half of this table is proposed by the spec author and has not |
| 25 | been ruled on.** Do not read a proposal as a ruling. |
| 26 | |
| 27 | | # | Decision | Rejected | Who, when | |
| 28 | |---|---|---|---| |
| 29 | | 1 | **Fichas is a fifth per-team instance app**, own DEK, own origin, beside docs, sheets, calendar and memoria | Cards as a memoria feature, which would make memoria an app of record and break its decision 7; cards as a Sheets view, where the unit of record is a cell in a grid and a ficha's is a typed record with relations | Paul, 2026-09-19 | |
| 30 | | 2 | **Every change to every object is logged: what changed, and who did it** | β | Paul, 2026-09-19 | |
| 31 | | 3 | **DabbleDB is the ancestor, not Access.** Steal schema-by-refinement, entity promotion and views-as-objects. Refuse the generic-database-builder positioning | "Access for Oficina" β the framing whose best execution (DabbleDB) still found no market | Paul named both ancestors 2026-09-19; the positioning conclusion is **proposed** | |
| 32 | | 4 | **Cards and entries are different objects.** A card is a mutable entity; an entry is an append-only event belonging to a card | One object type with a flag, which is what Hypercard and Access both did and why their lifecycles blur | **Proposed** | |
| 33 | | 5 | **A ficha holds a reference β `(app, instance, item)` β never a copy** | Copying sibling content into fichas, which makes fichas a second app of record for content it did not author | **Proposed** | |
| 34 | | 6 | **One stack ships first β Customer + Call Log β hard-coded. No type designer** | Shipping the generality first, which is the failure mode of every app in this lineage | **Proposed** | |
| 35 | | 7 | **SQLite, cgo-free (`modernc.org/sqlite`), as every sibling** | An embedded document store, whose entire value is querying *into* values β forbidden here, because values are ciphertext (Β§4) | **Proposed** | |
| 36 | |
| 37 | ## 2. What fichas is, and is not |
| 38 | |
| 39 | **Is:** a per-team instance app holding typed records and the append-only |
| 40 | event streams attached to them, with references out to items in sibling |
| 41 | apps, and a derived summary per card that cites its own entries. |
| 42 | |
| 43 | **Is not:** |
| 44 | |
| 45 | - **Not a database builder.** Β§1 decision 6. |
| 46 | - **Not an app of record for anything it did not author.** A reference is |
| 47 | not custody. Resolving one is the referenced app's decision, every time. |
| 48 | - **Never an actor.** Ruling 9: *"A write names its real actor. No sibling |
| 49 | ever acts as a person."* A generated summary is fichas writing as itself. |
| 50 | - **Never a second calculator**, following memoria decision 10's shape: if |
| 51 | a number belongs to a Sheet, the card cites the cell. |
| 52 | |
| 53 | ## 3. The data model |
| 54 | |
| 55 | Four objects. Two of them version (Β§5), two do not. |
| 56 | |
| 57 | - **Card** β a mutable entity. `(id, type, fields, created, revision)`. |
| 58 | Versions. |
| 59 | - **Entry** β append-only, belongs to a card. `(id, card_id, kind, body, |
| 60 | actor, at)`. **Immutable once written**, so it has no version history β |
| 61 | only a creation record. A call log is a fact about a past moment and |
| 62 | editing it is a different verb. |
| 63 | - **Field definition** β what a card type's typed fields are. Versions, |
| 64 | and its changes are migrations. |
| 65 | - **Reference** β `(app, instance, item)` pointing at a sibling's item. |
| 66 | Holds no copy, no title, no snippet (Β§4, Β§6). |
| 67 | |
| 68 | **Why the card/entry split is the whole model.** Entities get corrected; |
| 69 | events never do. It also gives the derived summary a clean definition: a |
| 70 | function over a card's entries, recomputed when one lands (Β§7). |
| 71 | |
| 72 | ## 4. The seal boundary β the real schema work |
| 73 | |
| 74 | Values are sealed. The sibling pattern is sealed blobs, plus deliberately |
| 75 | exposed plaintext coordinates, plus **keyed one-way fingerprints** β |
| 76 | memoria's `body_print`, `path_print`, `asker_print`, each documented as |
| 77 | one-way "so the bucket does not confirm" a guess. |
| 78 | |
| 79 | **So the design question that looks like storage is actually a leak-list |
| 80 | question:** which card fields leave the seal? That decides what can be |
| 81 | filtered, sorted and grouped, and nothing else does. |
| 82 | |
| 83 | Three tiers, and every field must be assigned one: |
| 84 | |
| 85 | | Tier | Queryable as | Cost | |
| 86 | |---|---|---| |
| 87 | | **Plaintext** | anything β ranges, ordering, grouping | in the bucket, in the clear. Sheets already trades this way for cell coordinates | |
| 88 | | **Print** (keyed, one-way) | **equality only** β never ranges, never ordering | leaks equality classes: two cards with the same value are visibly the same | |
| 89 | | **Sealed** | not at all | invisible to every query | |
| 90 | |
| 91 | Worked: *"prospects with no call in six weeks"* needs an order-preserving |
| 92 | timestamp, so **plaintext**. *"Group by stage"* needs stage as plaintext or |
| 93 | print. *"Notes containing X"* needs FTS5 over content the index can read β |
| 94 | which is the same decision memoria Β§7 already made for its chunk store, and |
| 95 | it is the expensive one. |
| 96 | |
| 97 | **Get this list right and the storage engine is an implementation detail. |
| 98 | Get it wrong and no engine helps.** This list does not exist yet and slice |
| 99 | 1 cannot start without it. |
| 100 | |
| 101 | ## 5. The event log β versioning, audit and S1's feed are one table |
| 102 | |
| 103 | Decision 2, built by copying Sheets rather than inventing: |
| 104 | |
| 105 | ``` |
| 106 | events(id, kind, object_kind, object_id, actor_id, payload_sealed, |
| 107 | trigger_id, created_at) |
| 108 | ``` |
| 109 | |
| 110 | Sheets' table is `events(id, kind, tab_id, actor_id, payload_sealed, |
| 111 | trigger_id, derived_count, created_at)`, `payload_sealed` under its own |
| 112 | seal purpose `"event.payload"`. **Copy the shape; do not drift from it.** |
| 113 | |
| 114 | **`trigger_id` is why.** Sheets uses it for "the input event that caused |
| 115 | this recalc". Fichas uses it for the derived summary: the regeneration |
| 116 | event names the entry that caused it, so *who did it* stays answerable when |
| 117 | the answer is "fichas did, as itself, because of that entry by that |
| 118 | person." Ruling 9 supplies the actor vocabulary β a member, a guest who |
| 119 | signed in and took a grant, or a sibling as itself. **Never attribute a |
| 120 | generated summary to whoever triggered it.** |
| 121 | |
| 122 | **Retention, and the reason it is not a detail.** A log of *values* is a |
| 123 | second sealed copy of everything an object ever held, forever, and fichas |
| 124 | holds notes about named third parties who never signed up. memoria Β§5.2 |
| 125 | calls deletion "the hard case"; ruling 14 makes retirement stated and |
| 126 | timed. **A card that is deleted while the log still holds its old values |
| 127 | makes "delete" a lie.** |
| 128 | |
| 129 | - **Kept permanently:** kind, object, actor, timestamp, **which fields |
| 130 | changed**. Survives the object's deletion β tombstone the payload, keep |
| 131 | the actor, because *"who deleted this"* is the question an audit log most |
| 132 | needs to answer. |
| 133 | - **Kept for a bounded window:** sealed before/after **values**. memoria's |
| 134 | breaker counters are the precedent at 8 days, and that spec calls the |
| 135 | retention "load-bearing rather than tidy". |
| 136 | - **Not kept:** full prior-state snapshots. Restore-to-version is a |
| 137 | different feature, is the expensive one, and is the one that makes |
| 138 | deletion undeliverable. **Not requested; do not build it by accident.** |
| 139 | |
| 140 | **This table is also S1.** An append-only log with an autoincrement id *is* |
| 141 | "an opaque position in the sibling's event log, issued by the sibling, |
| 142 | stored per consumer". Decision 2 and the sibling contract are the same |
| 143 | table, which is the largest single saving in this document. |
| 144 | |
| 145 | ## 6. The suite contract β fichas serves first |
| 146 | |
| 147 | `suite/README.md`: the server half "arrives with the first app that serves |
| 148 | one, and the fixtures get corrected by the second." memoria is the module's |
| 149 | only importer today and is a pure consumer. **Fichas would be the first |
| 150 | server**, with memoria as the waiting consumer β a real end-to-end exercise |
| 151 | of the contract, and the first-server design tax, both. |
| 152 | |
| 153 | S1 is Β§5's table. S2 answers per asker, intersected with custody. S3 is |
| 154 | person-less and custody-scoped. **S9 (reach) depends on an unresolved |
| 155 | ruling β see Β§10.** |
| 156 | |
| 157 | ## 7. The derived summary |
| 158 | |
| 159 | A function over a card's entries. Recomputed when an entry lands, |
| 160 | **never hand-edited**, and **it cites the entries it is built from**. |
| 161 | |
| 162 | An uncited status blob sitting beside a citation-first search engine |
| 163 | poisons the trust claim by proximity; memoria Β§8.4's "citations with an |
| 164 | optional answer, never the reverse" is the shape to hold to. The summary |
| 165 | is the app's own derived artefact over its own content, so it raises no |
| 166 | custody question β but under memoria Β§8.3 it is a model call, and Β§8 of |
| 167 | the suite doctrine governs the egress. |
| 168 | |
| 169 | ## 8. Capture β the wedge, and the reason this is not just a nicer Access |
| 170 | |
| 171 | **Data entry is why CRMs die, not the data model.** Folk wins on the email |
| 172 | sidecar and the clipper. A beautiful empty database is the standard |
| 173 | outcome. |
| 174 | |
| 175 | Calls is the asymmetry Folk cannot copy. A prospect call run over Calls |
| 176 | already has a room, a scheduled time, participants and a chat transcript, |
| 177 | inside the suite. **A call-log entry that writes itself** β date, who, |
| 178 | duration, chat β leaving two lines of substance to add, is a different |
| 179 | product from one typed from memory two days later. |
| 180 | |
| 181 | **If slice 1 ships without capture from Calls, it is a nicer Access.** |
| 182 | |
| 183 | ## 9. Reach, and the card that differs per viewer |
| 184 | |
| 185 | Under the cross-rung reach amendment (`suite/docs/superpowers/specs/2026-09-19-cross-rung-reach-draft.md`), |
| 186 | a reference resolves **at render, per asker**. Consequences, all decided |
| 187 | there and restated here only as they bind fichas: |
| 188 | |
| 189 | - **The same card shows different things to different people.** Two members |
| 190 | open one Customer card; one sees four linked documents, the other two. |
| 191 | Correct, and it will read as broken the first time it happens. |
| 192 | - **"2 references you cannot open" is forbidden.** It says "this exists and |
| 193 | you may not have it" β the fact the doctrine hides by 404ing rather than |
| 194 | 403ing. **Render nothing, and never a count.** |
| 195 | - **A stored reference to a non-team item inside a team-visible ficha is |
| 196 | at-rest knowledge that a private item exists, in a second app.** This is |
| 197 | why S1's visibility flag was removed. Unresolved. |
| 198 | |
| 199 | ## 10. Open |
| 200 | |
| 201 | - **NEEDS PAUL β the reach credential.** Fable recommends (a) + a |
| 202 | sibling-side designation; Astra recommends (c); the spec author |
| 203 | recommends the third path neither was asked about β **source-enforced |
| 204 | standing "offer to fichas/memoria" grants**, for which memoria Β§3.5's |
| 205 | published-only mode ("the sibling carries an explicit 'offer to memoria' |
| 206 | mark that *it* evaluates") is the existing precedent. It needs no new |
| 207 | credential and is not blocked on the home's identity layer. **Unruled; |
| 208 | Β§9 and Β§6's S9 line both wait on it.** |
| 209 | - **The seal-tier list (Β§4) does not exist.** Slice 1 is blocked on it. |
| 210 | - **Third-party personal data.** Fichas holds notes about people who never |
| 211 | signed up. That is a lawful-basis and subprocessor question, not a |
| 212 | retention one, and TensorX's zero-retention posture does not answer it |
| 213 | (`memoria/ZDR-TREAT-AS-LOCAL.md` Β§6). Not a blocker at one user; a |
| 214 | blocker at the second team. |
| 215 | - **Whether fichas embeds at all**, or is keyword-only in memoria. |
| 216 | |
| 217 | ## 11. Deliberately not built |
| 218 | |
| 219 | | Thing | Trigger | |
| 220 | |---|---| |
| 221 | | A type designer | A second stack asks for it, in use, not in principle | |
| 222 | | Restore-to-version | Paul asks for it. Β§5 says why it is not free | |
| 223 | | Reshaping by sentence β "split this column", "these are the same company" | **Slice 2. It is the thesis** (decision 3: the LLM is what replaces HyperTalk, and the reason this is not a 2008 rerun) β but it needs data to reshape, and there is none until slice 1 has been lived in | |
| 224 | | Editing an entry | Never as an edit. A correction is a new entry that supersedes (Β§3) | |
| 225 | | Cross-team anything | Never, under suite ruling 2 | |
| 226 | |
| 227 | ## 12. Slice 1 |
| 228 | |
| 229 | In order, and small: |
| 230 | |
| 231 | 1. **The seal-tier list** (Β§4). Blocking. |
| 232 | 2. Card + entry + event log (Β§3, Β§5), one hard-coded stack: Customer, |
| 233 | Call Log. No type designer. |
| 234 | 3. Capture from Calls (Β§8). |
| 235 | 4. The S1/S2/S3 server half and the conformance harness in CI (Β§6), so |
| 236 | memoria indexes fichas like a Doc. |
| 237 | 5. The derived summary, with citations (Β§7). |
| 238 | |
| 239 | References and reach (Β§9) wait on Β§10's ruling. **Reshaping by sentence is |
| 240 | slice 2 and must not leak into slice 1.** |
| 241 | |