846c87629fd3f74d2f3cacccd160359932986dc7
32
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
37be3b0bff |
feat(6849): the command-line restore uses the same rollback, and the docs say how restore works now
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 31s
CI & Build / Python tests (push) Successful in 1m39s
CI & Build / integration (push) Successful in 1m49s
CI & Build / Build & push image (push) Successful in 31s
python -m grimoire.restore <file> prints what the restore would change (the same preview as Settings) and stops. With --yes it runs backup_job.rollback_to_backup: the current state is saved first, then the whole instance goes back to the backup. An empty instance takes any full backup. A live one only takes a backup of its own, and otherwise exits 3 with the reason. A v1 backup keeps the copy-in path into an empty instance. docs/backups.md Restoring is rewritten: - what a restore puts back and removes; - the same-instance rule; - the safety backup; - what survives; - the web and command-line ways in. Its S3 key advice now asks for read as well as write when restoring from the bucket. README, features, architecture and the API reference follow. Milestone 582, step 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1f5e963c35 |
feat(6848): Settings restore rolls back, from the destination or a file, after a preview
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / Python lint (push) Successful in 1s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 1m42s
CI & Build / integration (push) Successful in 1m48s
CI & Build / Build & push image (push) Canceled after 22s
The web restore no longer copies a backup in beside the live data. - GET /api/admin/restore/sources lists the backups at the backup destination, newest first. - POST /api/admin/restore/preview says what a restore would do, changing nothing: fresh or rollback, when the backup was taken, and counts now and after per kind. A backup that cannot apply here gets a 409 with the reason. - POST /api/admin/restore?confirm=rollback runs backup_job.rollback_to_backup: safety backup, replace, then search rebuilt in the background. Each takes ?source=<name> (a backup at the destination) or an upload. The destination path exists because uploads are held to the request-size limit, and a real instance's backup is far over it. backup_s3.get_file downloads from a bucket. backup_destination.list_backups/open_backup only accept names the scheduled backup writes, so a request cannot reach any other file. Settings -> Data -> Restore from Backup opens RestoreBackupDialog. The dialog: 1. Pick a backup at the destination or a file. 2. Read the preview. 3. Type "roll back". 4. Run it. The dialog then says where the replaced state was saved, and sends the user to sign in again. Closes #6840. Milestone 582, step 4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
34f8d7060b |
feat(6847): a rollback first backs up the state it replaces
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / integration (push) Successful in 1m49s
CI & Build / Python tests (push) Successful in 1m40s
CI & Build / Build & push image (push) Successful in 24s
backup_job.rollback_to_backup: 1. Checks the backup can apply here (backup.check_replace, which shares _replace_mode with replace_from_backup), refusing before anything is written. 2. Takes the backup lock, so no scheduled backup runs during the rollback. 3. Saves the current store with save_state_before_restore. 4. Replaces the store. The safety file goes to the backup destination when backups are on and it takes it. Otherwise it goes to DATA_DIR/pre-restore, with a note when the destination refused. If neither takes it the rollback does not run. The result names where the file went. Milestone 582, step 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8806ab85b4 |
feat(6846): replace_from_backup rolls the store back to a full backup by id
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m38s
CI & Build / integration (push) Successful in 2m1s
CI & Build / Build & push image (push) Successful in 20s
One transaction: - Refuse unless the backup is a full one and, on a live instance, carries this instance's id. A fresh instance (no records, at most the restoring account) takes any full backup and adopts its id. - Clear first: search vectors and the CSS consumer map (derived); every backed-up table nothing outside the backup points at, whole; the settings the backup carries; and the identity-table rows the backup lacks. A share granted by a removed user goes before the user. - Write the backup through the same _build_* helpers as the copy-in restore, now _load_v2(keep_ids=...). Identity tables (users, projects, milestones, notes, rulebooks, topics, rules, systems, design systems, code shapes, family decisions) go through _place. That keeps the backup's id and merges, so API keys, shares and forge connections of a surviving row stay. - Reset id sequences past the restored max, and sign every session out. reindex_after_restore runs the four embedding backfills afterwards. A unit guard checks that no row kept for outside references cascades from a table replaced whole. Integration tests cover edits, deletions and new rows put back, a surviving API key, sessions ending, fresh ids afterwards, foreign or id-less backups refused, a user export refused, a failure part-way changing nothing, and a fresh instance adopting the id. Milestone 582, step 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6d8db0bb65 |
feat(6845): every instance has an id lineage, and every full backup carries it
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / Python tests (push) Successful in 1m37s
CI & Build / integration (push) Successful in 2m7s
CI & Build / Build & push image (push) Successful in 22s
instance_identity is one row made by migration 0127 with a random id, so an instance has it before its first account. A full backup carries it in its header as instance_id (the tar manifest header and the JSON top level); BACKUP_VERSION is 28. The table rides in _NOT_INCLUDED: a restore decides from the header value rather than copying a row. Two stores with the same id number their rows the same way. That is what lets a later step roll a live instance back by original id, refuse a backup whose ids would land on unrelated rows, and have a fresh-instance restore adopt the backup's id (adopt_instance_id). Milestone 582, step 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
dbd6d5b8b5 |
feat(6838): read the client address and HTTPS from the trusted proxy hop
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 37s
CI & Build / Python tests (push) Failing after 1m20s
CI & Build / integration (push) Successful in 2m27s
CI & Build / Build & push image (push) Skipped
grimoire/proxy.py is the one reader of X-Forwarded-For and X-Forwarded-Proto. It takes the entry TRUSTED_PROXY_HOPS in from the right (default 1; a shorter header reads its leftmost entry; 0 trusts no proxy), so an entry a client puts left of the proxy's is never believed. Before this, with TRUST_PROXY_HEADERS on, the leftmost X-Forwarded-For entry keyed the login, register, forgot and reset rate limits and the audit log, and a client could choose it. The session cookie is Secure, and HSTS (max-age=31536000) is sent, exactly when the request is HTTPS by the trusted proxy's word. Plain HTTP is untouched and nothing redirects. SECURE_COOKIES is gone (a startup warning says so when it is still set); TRUST_PROXY_HEADERS=false still maps to 0 hops, with a warning to move to TRUSTED_PROXY_HOPS. Admin audit lines now use the same client address instead of the socket peer. The placeholder-key refusal keyed on SECURE_COOKIES, so it now refuses either placeholder key outright; the instance makes its own key when none is set. Family idea #5004 (IsHTTPS, trusted-hop X-Forwarded-Proto), canon v2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b87178d261 |
fix(6495): a name defined in two packages counts only in the canon's own
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / integration (push) Successful in 1m22s
CI & Build / TypeScript typecheck (push) Successful in 37s
CI & Build / Python tests (push) Successful in 1m59s
CI & Build / Build & push image (push) Successful in 43s
A by-name reference edge could not tell two definitions of one name apart: in a Go repo that defines `file` as importRun.file (internal/files, the canon) and as sftpRun.file (internal/replication), a mirror test calling `m.file(` was recorded as a use of the canon. names_canon now takes defined_at, every repo path that defines each name, in place of the shape file's own names. The own-file rule is unchanged. When the repo also defines the canon's name somewhere the canon is not recorded, a mention counts only from the canon's own package: the directory of a file location exactly, or under a directory location. A name defined once resolves as before. Proposer version 8 -> 9, so the next coverage refresh re-reads every row and replaces its reference edges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2a6d9080ba |
fix(6477): a plain-word call needs the parenthesis right after the word
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / integration (push) Successful in 59s
CI & Build / Python tests (push) Successful in 1m37s
CI & Build / Build & push image (push) Successful in 26s
Read against Fabled Archives after the cleanup: of the 9 rows still linked to snippet #5448 (symbol `file`), all but one matched prose in a comment, "a file (not a directory)", "the file (ScanWaiting". The only real call was `r.file(ctx, ...)`. Formatted code never puts a space before a call's parenthesis and prose does, so the plain-word test is now `word(` with no whitespace. _PROPOSER_VERSION 8 re-reads every row once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cdb71e64c4 |
fix(6477): a body uses a canon by name only when the name can resolve to it
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 43s
CI & Build / integration (push) Successful in 1m36s
CI & Build / Python tests (push) Successful in 2m7s
CI & Build / Build & push image (push) Successful in 42s
The proposer's reference basis, and every uses edge it writes, counted any word-bounded mention of a canon's symbol. Three holes made that wrong at scale (snippet #5448, symbol `file`, held 2,161 uses edges): - Go, Rust, Kotlin, Swift, Dart, C, C#, Ruby and PHP were outside the language-family maps, so their canons read as "unknown, no gate" and were matched in every Python, TS and CSS body. - Canons in other projects were matched by name, though one repo never calls a function defined in another's. - A plain word (`file`, `seen`) or a module's own helper (`_run`) matched wherever it appeared. names_canon is now the one by-name test, used by match_canon, reference_canons and the write-path hint: same project, not a name the file defines itself (unless the canon is recorded there), no generic verb with or without a leading underscore, and a bare lowercase word only where it is called. The write path keeps the payload's own names in play, since a redefinition of a pulled canon is the copy it exists to flag. Repair: record_uses only adds, so replace_reference_uses now makes a row's machine-written reference edges exactly what the current rule finds, leaving judged and hook edges alone. _PROPOSER_VERSION 7 re-reads every row on its project's next refresh. Migration 0125 deletes the cross-project reference edges already stored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7faaa311d7 |
fix(6470): a moment proposal counts only opens no hook explained, and asks the reader to judge it
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m46s
CI & Build / Build & push image (push) Successful in 37s
The signal counted every get_rule open against every moment from the previous three minutes, so a rule a search arm had just shown was proposed for whatever moment happened to fire. All nine pending signal proposals were wrong. - The hook skips an open of a rule a hook named this session, and sends the last three acts from 90 seconds instead of 30 from 180. - A rule already mounted anywhere gathers no signal evidence. - A signal pair keeps the actions that reached its moment and shows them as its why. - The proposal line says it is co-occurrence, not a recommendation, and asks the reader to check the rule against the moment before offering it; moments.md says the same. Plugin 2026.10.26.2029. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
db2427a3dc |
feat(6249): the write-path hint asks whether a new definition copies a close snippet
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Successful in 1m56s
CI & Build / Build & push image (push) Successful in 22s
The derive check matches by name, so a renamed copy of a recorded snippet passed it and showed only as "similar" prior art. When a write introduces a definition the file does not hold (shape_ledger.new_shapes_at) and an unseen snippet on the menu clears the write-path floor by WRITEPATH_COPY_MARGIN (0.80 at the default floor), the hint now says to reuse it or extract the shared shape before another copy. Names the derive check or the stamping feed already spoke for are left to them. Covers recorded snippets only: the ledger keeps no bodies to compare an unrecorded sibling against. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0a4eb313fe |
fix(6255): Test destination names an address that did not answer as S3
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m5s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 23s
A 4xx with no S3 error body came from something else at that address, most often a web server on the default port. The message now says so and to check the scheme and port, beside the raw status. A 5xx without a body keeps its own reason, since a busy S3 service can answer that way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d51ccd9b69 |
fix(6253): the moment-proposal queue lists a signal pair only once it has proposed
pending() listed every suggested signal row, including pairs still gathering evidence below the three-situation bar, so the queue held 200 "proposals" where about 25 had crossed it. A signal pair now needs proposed_at in its evidence, the same test the misfire half already applies; proposals from a pass are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
95ef4af746 |
fix(6251): the rules warning sends its reader to the rule-arm review
surfaced_never_pulled told a rules reader there was no judged sample for the rule arms. There is one now; the guard tying the text to REVIEWABLE caught it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7c1d98dd2a |
feat(6251): the rule arms can be reviewed — menus_to_review re-runs prompt_rule, pre_tool_rule and write_path_rule
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped
A rule arm logs its query, floor, budget and project, which is everything semantic_search_rules takes, so a logged call is re-run to a depth past its budget and judged like an auto_inject menu. within_budget is the arm's own cut: inside the budget and, for the banded command and write arms, inside the band of the top hit. "Opened after" reads rule pulls, and rules created after the call are dropped as notes are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
dac4c59e07 |
fix(6252): a prompt that names nothing gets no notes menu
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m3s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped
"keep going", "yes please do", "is it deployed?": with no word of its own the
auto-inject query was the previous reply's tail alone, and the menu handed
back the records that reply was built from. In a sample of 59 logged calls,
28 came from short prompts; those with a subject of their own ("continue into
DRY 14", "fix the Windows gate") led with the record meant, while the ones
without were finished tasks already in context, with an open on 2 of about 15.
_has_subject checks the operator's words against a list of words that name
nothing; without one, the ranked menu is skipped and records named by number
are still shown. The prompt rule arm is unchanged and still searches with the
tail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
1fa9245bbf |
fix(6247): a getter is seen by its pulls, so the silent list stops naming every pull door
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m5s
CI & Build / Python tests (push) Successful in 1m56s
CI & Build / Build & push image (push) Successful in 22s
usage and rule_usage gain pulled_by_source, read from the per-source rows the readout already had, and _silent_surfaces counts those doors as having emitted. The rules-listing points were seen as of the previous commit, through rule_usage.by_source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d05d4f3632 |
feat(4636): rule pull-through per surface, and per-rule usage with confirmed lessons on list_rules
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m3s
CI & Build / Python tests (push) Successful in 1m56s
CI & Build / Build & push image (push) Successful in 22s
retrieval_telemetry gains rule_usage.by_source, the twin of usage.by_source: distinct rules each surface showed, how many an agent opened, and a ratio that is null on a bulk delivery. list_rules(with_usage=True, days=N) adds each rule's usage, windowed, and how many lessons are confirmed instances of it, so linked and unlinked rules can be compared through MCP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
71260a28e2 |
align(4909b): every write with a path gets its rule check, whether or not prior art matched
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m56s
CI & Build / Build & push image (push) Successful in 22s
The write-path rule arm ran only past the notes guard: a write whose code matched no snippet, issue or lesson returned before any rule was searched. It now runs before that guard, on `code or path`, and a write that matched nothing but a rule still returns the rule lines. Operator ruling (2026-10-05): yes. A rule about the file being written does not depend on a snippet resembling it. Cost: one more rule search per write that matched no prior art, with its own embedding (the query is `code or path`, not the notes arm's concept query, so the per-request memo does not cover it). Plus the preference slot from (c) on those writes. Expected telemetry shift, to be read on its own once this reaches main: write_path_rule calls rise toward write_path's count (14 days to 2026-10-22: write_path_rule 4,634 against write_path 3,786 — the notes arm skips trivial payloads, the rule arm does not, so the rule arm will now exceed it). Its zero-result share rises too, since the newly covered writes are the ones that matched nothing in the notes corpus. Tests: the gate test is inverted (a write with no prior art still gets its rule check); the design-only write now runs the rule arm on its path; two notes-arm tests read the write_path row instead of any row, since the rule arm writes its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c3933507c9 |
align(4909c): the command and write rule arms reserve a line for a preference
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped
PRE_TOOL_RULE and WRITE_PATH_RULE now take preference_slot=True, like
PROMPT_RULE. When no preference placed on score, the arm asks once more for
the best preference over the same bar, and shows it as one extra line.
Operator ruling (2026-10-05): yes. A preference about how to act ("let each
action land before the next") is ranked against every rule whose trigger
names the same command; the act is its last chance to shape anything.
Cost: one kind-filtered search per call that placed no preference. The
query embedding is memoised per request on both hook routes, so no extra
model call. Expect a new stream of preference_slot rows from the two act
arms; preference_slot's call count will rise roughly by pre_tool_rule's and
write_path_rule's (~23k calls over 14 days before this change), and its
zero-result share will rise with it, since most commands have no matching
preference.
Tests: the tool-arm call-row tests count rows under pre_tool_rule only (the
slot logs under its own name), and the write-arm bar test reads the arm's
own search, which is the first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
6299335254 |
fix(4909d): a shared floor move records the named surface first, then its siblings
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m9s
CI & Build / Python tests (push) Successful in 1m58s
CI & Build / Build & push image (push) Successful in 33s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c8ede427ca |
align(4909d): every rule arm that annotates reads one bar, kb_rule_threshold at 0.70
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m7s
CI & Build / Python tests (push) Failing after 1m30s
CI & Build / Build & push image (push) Skipped
prompt_rule, pre_tool_rule and write_path_rule now share one settings key, RULE_FLOOR_KEY = "kb_rule_threshold", default 0.70. The reply backstop keeps its own 0.80, because there the floor is a stop bar. Moving the shared floor through tune_retrieval or the Settings form records a tuning event on every surface that reads it (set_dial, via retrieval_surfaces.sharing_floor), and the result names the surfaces it carried as `moved_with`. Settings shows one "Standing-rule confidence threshold" control where it showed three. Operator ruling (2026-10-05): one shared bar for the rule arms, "especially since we're only taking the first x number of them" -- the budget limits what is shown; the bar only decides what is worth ranking. Kept a dial. Why 0.70, from records read rather than percentiles: - write_path_rule near-misses at 0.7199-0.72 were all false positives (rule 149 against a Lidarr comment, 157 against a retention Svelte file, 121 against an Archives restore, 164 against a config test, 131 against a Go package), so the old 0.72 was not hiding real hits -- but the band does noise control on this arm, so dropping 0.02 costs little. - prompt_rule: the operator set 0.70 by hand on 2026-09-18, after a 0.60 experiment admitted junk and was reverted. 0.70 is the number with a human decision behind it. - pre_tool_rule lines near 0.68 this session were mostly noise; rising to 0.70 trims them, and the band still narrows what clears. Retired keys: kb_promptrule_threshold, kb_toolrule_threshold and kb_rulehint_threshold are no longer read. Rows an install stored under them stay in the settings table, inert. On this instance the effective bars move pre_tool_rule 0.68 -> 0.70, write_path_rule 0.72 -> 0.70, prompt_rule stays 0.70; record_release_defaults writes those per-surface release rows on boot, so the telemetry shift has an event to explain it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f77d18cb63 |
fix(6245): a re-run of bind_repo(moved_from=…) finishes an earlier move
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 59s
CI & Build / integration (push) Successful in 1m25s
CI & Build / Python tests (push) Successful in 2m20s
CI & Build / Build & push image (push) Successful in 32s
Once the binding has moved, the old remote is no longer bound, and move_binding refused. An install that renamed its repo before snippet locations moved with it had no way to move them. When the old remote is unbound and the new one is bound to this project, bind_repo now skips the binding move and still moves the snippet locations that name the old repo. The snippet-move test gives each location its own path: rewritten to one repo, identical locations merge into one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e89e6a24de |
fix(6245): bind_repo(moved_from=…) moves the snippet locations recorded in the old repo
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 58s
CI & Build / integration (push) Failing after 1m16s
CI & Build / Python tests (push) Successful in 2m33s
CI & Build / Build & push image (push) Skipped
A repo renamed on its forge kept every snippet location naming the old repo: the binding and the shape ledger moved, the snippets did not. - snippets.move_location_repo rewrites, on every live snippet of the project, each location whose repo is the old remote in any form (URL, normalized key, "owner/Repo", bare "Repo", compared without case) to the new normalized key. A snippet the caller can only read is listed in read_only and left alone. - Its loop is shared with move_locations through _rewrite_locations, which saves each changed snippet through update_snippet. - bind_repo calls it after the binding moves and reports snippets_moved and snippets_read_only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
787685ac63 |
fix(6244): the instance keeps its own SECRET_KEY in /data, and docker-compose.yml is the compose users deploy from
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m9s
CI & Build / Python tests (push) Successful in 2m7s
CI & Build / Build & push image (push) Successful in 30s
With no SECRET_KEY set, Grimoire fell back to a fixed placeholder. The quickstart compose substituted change-me-in-production, so the README's own update step (pull, up -d) recreated the container on that placeholder, and every credential sealed under the key the user started with stopped opening. - config: with neither SECRET_KEY nor SECRET_KEY_FILE, the first start writes a random key to DATA_DIR/secret_key (mode 0600, published with a hard link so concurrent starts agree), and later starts read it back. An explicit key wins and leaves the file alone. A missing SECRET_KEY_FILE or an empty key file stops the start instead of switching keys. With no data directory (a run outside the container) the process gets a temporary key and says so. - app: warns on a public placeholder key, on a temporary key, and when /data is not a mounted volume. - Dockerfile creates /data. - docker-compose.yml is now the deploy compose (published image, both volumes, nothing to set). The build-from-source compose is docker-compose.dev.yml, and docker-compose.quickstart.yml is gone. The Swarm stack file gains the /data volume and keeps its explicit key, because a Swarm can move the app away from a node-local volume. - README: a Deploy section with the compose inline, SECRET_KEY optional, "keep both volumes", and updates that keep the key. A test holds the README's copy equal to the file. - Installs that ran on the change-me-in-production placeholder get a key of their own on the new compose; Settings then asks once for saved forge tokens and the backup secret. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
979efe6662 |
feat(6243): move_path carries a moved path's snippets with its shapes
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 17s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m9s
CI & Build / Python tests (push) Successful in 2m12s
CI & Build / Build & push image (push) Successful in 31s
Snippet locations are keyed by path too, so a snippet recorded under a moved directory reads `missing` at its next check. The tool is now move_path(project_id, from_path, to_path). It moves the ledger rows as before, and re-paths the project's snippet locations under from_path through update_snippet, so each body and its indexed mirror stay one record. A snippet the caller can only read is listed in snippets_read_only. The tool refuses only when nothing at all is recorded under from_path. The path test is the one snippets already uses (_path_touches), and the plugin version is minted for the skill edit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
47b52197aa |
feat(6243): a directory moved in the tree keeps its shape judgments
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Failing after 15s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m15s
CI & Build / Python tests (push) Successful in 2m9s
CI & Build / Build & push image (push) Successful in 30s
Ledger rows are keyed by path, so after a directory moves the next sync marks every judged row vanished and lists the same shapes again, unjudged. move_shapes(project_id, from_path, to_path) re-keys the rows under one path to the other, in every repo of the project, keeping each row's judgment, uses edges and history and adding a `moved` event that records the path it left. A row already at the target is replaced when nobody judged it; a judged one stands, and the old row is reported in `kept`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5ad1df8e0f |
feat(6242): a repo renamed on its forge keeps its binding and shape ledger
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 1m13s
CI & Build / integration (push) Successful in 1m15s
CI & Build / Python tests (push) Successful in 2m8s
CI & Build / Build & push image (push) Successful in 28s
A binding and every row of its project's code-shape ledger are keyed by the repo's remote. A rename on the forge changes the remote, so binding the new name started an empty ledger beside the old one and left every judgment behind. bind_repo now takes moved_from, the old remote. Its binding and the project's ledger rows move to the new key in one transaction, and the reply says how many shapes moved. The move is refused when: - the old name isn't bound to that project; - the new name is already bound; - the project already has a ledger under the new name. Each refusal leaves both names exactly as they were. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a07547447f |
rename(547): the repo is FabledGrimoire, and no stray "scribe" is left
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m1s
CI & Build / Python tests (push) Successful in 2m2s
CI & Build / Build & push image (push) Successful in 28s
The forge repo is now bvandeusen/FabledGrimoire. The plugin marketplace default, the install commands in the docs and plugin README, the CI requirements title and the example repo URLs in comments and tests follow it. docs/development.md now names the image the build actually publishes, fabledgrimoire. Two test names still said "scribes_own"; they now say "grimoires_own". What still says scribe does so on purpose: - the backup reader's accepted archive names and prefix; - the label every stored secret is sealed under; - the notes on keeping an old install's database names; - the old migrations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
101ab7bc8d |
fix(design): the app serves its own fonts and names no other host
theme.css loaded Fraunces, Inter and JetBrains Mono from Google Fonts. That sent every visitor's address to Google on every page load, and an instance with no internet drew in fallback type. All three families are now vendored under frontend/src/assets/fonts with every subset Google publishes for them, plus their OFL licences. The Content-Security-Policy no longer lists fonts.googleapis.com or fonts.gstatic.com. A new guard test fails if any stylesheet, page or component links a remote asset or preconnects to one, or if the CSP names another host. The theme.css header now describes the sheet as it is instead of how it used to read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c5eeec471f |
rename(547): the plugin is grimoire — manifest, MCP key, hooks, env, skills
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped
Step 3 of the Scribe -> Grimoire rename. - Plugin `grimoire` in marketplace `grimoire-plugin`, MCP server key `grimoire`, so tools reach a session as mcp__plugin_grimoire_grimoire__*. - hooks/scribe_* -> hooks/grimoire_*, their shell functions and the TMPDIR ledgers with them; SCRIBE_URL / SCRIBE_TOKEN / SCRIBE_HOOK_DIR / SCRIBE_LEDGER_DIRS -> GRIMOIRE_*; the project marker is `.grimoire`. - skills/using-scribe -> using-grimoire, /grimoire:sync, stored processes arrive as grimoire-proc-<slug>; moment_actions names them the same way. - Migration 0124 rewrites stored skill names (rule_moments, rule_moment_judgments, moment_mappings) from scribe:/scribe-proc- to grimoire:/grimoire-proc-, so a rule mounted on a skill keeps firing. - Settings' install commands and MCP-only defaults, the docs and the tests follow. - scripts/check_plugin.py drops the smoke event for a report-check hook that has no file in plugin/hooks. Kept on purpose: the secret-sealing label, and the names that let backups written as Scribe restore and be counted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0f5ef0be63 |
rename(547): the server is grimoire — package, image, DB defaults, backups
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 44s
Step 2 of the Scribe -> Grimoire rename. - src/scribe -> src/grimoire, and every import, patch target, module path, hypercorn entry point and `python -m grimoire.restore` with it; pyproject, uv.lock and the frontend package are named grimoire. - Image git.fabledsword.com/bvandeusen/fabledgrimoire; CI's Postgres service runs as grimoire / grimoire_test. - Compose files default the DB user, database and (prod) volume to grimoire, each overridable from .env, so an install created as Scribe keeps its data by setting POSTGRES_USER, POSTGRES_DB and PGDATA_VOLUME. - Backups are written as grimoire-<ts>.tar with format grimoire-backup-tar. Archives named scribe-* with the scribe-backup-tar manifest still restore and are still counted at the destination (folder and S3), with tests. - Exports, the MCP auth realm, the ASGI scope key and the persistent logger carry the new name. Kept on purpose: the secret-sealing key label stays "scribe.secrets.v1", because every stored secret was derived under it. Plugin identity (plugin name, hooks, env vars, .scribe marker, skills) is step 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |