Spaces:
Running
Running
Commit History
-
feat(capability): typed .fq driver contract, Studio adapter, evidence-based closure
Wraps PostMeet as a resident capability and gives Studio a generic way to operate any typed .fq driver pack, with closure state computed from evidence rather than asserted by whoever wrote the document. Capability contract (core/capability_fq.py) Seven roles carried by a typed .fq: manifest, health, inventory, execute, status, evidence, receipt. Registration refuses a pack that advertises a role with no runtime behind it, and health is graded by the contract from reported checks — a driver cannot mark itself healthy. Studio adapter (core/studio_fq.py, /studio/fq) Renders any capability from describe() alone: identity, health, closure, actions, live jobs, evidence, receipts. Job phases and progress fields come from the manifest, so no product-specific branch exists — asserted by a test that greps the source for product names. Accepted is never success; only capability.receipt can report verified. Driver auth renders as status only, never as a secret. Promotion aut…249db15
-
install captured packages fleet-wide, behind a mandatory database snapshot
Verified signaturefe3666d -
onboarding: a dropped database becomes a mesh; capture subscription packages once
Verified signature091b6a4 -
editor: the fence gates reads, creation and navigation, not just writes
Verified signature095f130 -
editor: records live in the site's own database, not an erasable file
Verified signature6786699 -
editor: say plainly when held changes are not durable
Verified signature64e6181 -
editor: site tree becomes a collapsible rail, not a tab
Verified signature15a0921 -
editor: per-page scope, held changes, approvals, campaigns
Verified signature5f44965 -
editor: ship current UI (Site tree + History tabs)
Verified signature643d6c3
-
feat(owner): the mesh key signs in on its own, without going through Joomla
Owner scope reads the fleet and moves databases — all of it over MySQL, none of it touching a site's content — so requiring a Joomla Super User account to reach it was borrowing another system's identity for a job that system has no part in. It also made the mesh unreachable exactly when it was most needed: no site registered means no Joomla to authenticate against, so the operator could not get to the console whose whole purpose is registering the first site. The key alone now mints an owner session, ahead of any site lookup. Content editing is unaffected and still needs that site's own Joomla login, which is enforced where it always was, in `_sess`. The session carries no auth header at all — pinned, because one that did would be a leak away from being able to edit. Also fixed on the way: `registry_path` consulted FIXMESH_DATA but then returned a path resolved at import, so it answered with wherever the process started rather than where it was told to look. Co-Authored-By: Claude …94981f9 -
fix(migrate): the migration stream needed the prefix and the session too
It streams NDJSON so it cannot go through api(), which is exactly how it kept both faults the rest of the page just lost: a hard-coded root that reaches the host app when mounted, and no session header, so a framed owner console would have watched a migration refuse itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mudH38FU1L34mEeNJNgbBc4c6948 -
fix(session): a framed editor never got its cookies back
Signing in worked and everything after it said "Not signed in." The editor is framed by the Studio, which the Hub frames in turn, so its cookies are third-party twice over: SameSite=Lax meant the browser set the cookie on login and then declined to send it on any subsequent request. Two changes, because either alone leaves a browser out. The cookie is now SameSite=None with Secure, which is what a framed context requires. And the login hands the token back so the page can repeat it in X-Kyklos-Session, for browsers that decline third-party cookies outright regardless of attributes — one middleware merges that header into the cookies every existing guard already reads, so no route or permission check changed. Returning the token is a real trade: it is readable by the page rather than httpOnly. It is the same token the cookie carries and grants nothing a working cookie would not, and the alternative was an editor that cannot be used where it is embedded. Co-Authored-By: Claude Opus 5 …b8b0af4 -
fix(editor): mounted under the Studio, every call was going to the host's root
API_BASE was added when the editor was folded into the Studio, and this session's edits copied the standalone page over it — so every request went to "/" instead of "/api/editor". Sign-in 404'd, and the calls with a catch around them failed silently, which is why a broken sign-in looked like an empty site list rather than an error. Restored, and applied to the three paths that build their own URLs: the media thumbnail, the upload POST, and the fleet console's per-site link. The live preview path is built server-side and rooted at "/", so it is prefixed where it is consumed. Pinned: no fetch in the page may start at a hard-coded root. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mudH38FU1L34mEeNJNgbB1c1e1bf -
fix(editor): a duplicate declaration made every button on the page inert
The session helpers introduced a second top-level `drop`, and the page already had one. That is a SyntaxError, not a broken feature: the script never parses, so no handler binds and every button on every tab silently does nothing while the page looks entirely normal. Renamed to keptSites/keepSite/dropSite. The page is one file with one script block, which makes this failure mode both easy to cause and total when caused — so it is now checked. node parses the script, and a second check names any top-level identifier declared twice, which is the specific mistake rather than a parser message about it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mudH38FU1L34mEeNJNgbB51550a7 -
feat(resume): the browser remembers the connection, the deployment stores nothing
Replaces the encrypted Hub mirror from the previous commit. Mirroring solved the restart, but it solved it by keeping database passwords somewhere — which is the thing worth not doing. A deployment with no durable storage now writes no registry at all. A connection lives in the process while it runs, and comes back to the browser that made it; that browser keeps it and hands it back on the next visit, so returning to a restarted Space finds its sites where it left them. Clear the browser and it is genuinely gone, because there is no second copy anywhere. A deployment that does have a volume — the compose one — keeps writing its registry exactly as before. Durability decides, not a flag. The resume endpoint is unauthenticated and discloses nothing: the caller has to present complete database credentials, and anyone already holding those has no use for it. Editing whatever is resumed still needs that site's own Joomla login. Pinned: a resumed connection is readable in memory and appe…05acb21 -
feat(store): keep connected sites across restarts, without paid storage
A Space with no volume forgot every connected database on restart, and the sign -in said so without saying what to do about it. Neither needed paid storage: the Hub is already here, so the registry is mirrored into a private dataset repo owned by the same account, and read back once when the local copy is missing. It is encrypted before it leaves the process. The file holds live database passwords and a private repo is an access control, not a guarantee — a leaked token or a mistaken visibility flip would read them. The key is derived from a secret the deployment already has, so there is no second secret to rotate, and a deployment without that secret does not mirror at all rather than mirroring in the clear. The local file stays the source of truth: the mirror is written after a change and read only when there is nothing local, never on the request path, and a failed mirror never fails the save that triggered it. Also: a rejected owner key now names the setting it is compared again…e18c2b9 -
feat(commands): the sentence parser reads from the same vocabulary as the tools
Three lists had already drifted apart: the in-app agent recognised four nouns, the MCP surface offered twenty-five tools, and the element record knew thirty-nine types. An operator could ask for an accordion over MCP and be offered a blog post for the same words in the app. The parser's nouns are now derived from the element record rather than typed out, so anything the builder can make is something the agent can hear, and confirming a new element teaches both at once. The four hand-written nouns stay — they carry real disambiguation ("the lunch menu" is a page, "menu item" is navigation) that a generated pattern would lose. Item types are excluded on purpose: an accordion_item is a child of the accordion, and "add an accordion item" is a sentence about the accordion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mudH38FU1L34mEeNJNgbB21e9c28 -
fix(registry): write where the deployment can actually write, and say when it won't last
A Space with no persistent storage has no /data, and the code directory it was built with is not writable — so the editor resolved a path it could never save to, and reported an unreachable database when the database was never the problem. Worse, the two faces resolved *different* defaults, so the registry they were supposed to share was only shared where both happened to be writable. One resolution now, and dbmesh asks the editor for it: an explicit setting, an already-mounted /data, the checkout, the image's writable workspace, temp. /data is never created — inventing it would fake a volume and call the result durable. Where nothing durable exists the sign-in says so, rather than letting the operator find out by re-entering database credentials after every restart. The empty-registry message now points at the form on that same screen instead of sending people to another tab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mud…34d6cd6 -
feat(editor): sign in and connect a database from the one screen
The editor had to be entered through another face: with nothing registered there was no way in, and with several registered it silently signed you into whichever happened to be first. The sign-in now carries a mesh picker — ids only, no URLs and no credentials, which is enough to choose one and useless on its own — and a Connect a database section that opens by itself when the registry is empty. Adding a site accepts the owner key without a session, and only there. A fresh install has no Joomla to authenticate an owner against, so requiring one was a deadlock; the boundary holds, because the key can only add a database whose credentials the caller already has, and editing that site still needs that site's own Super User. Pinned in test_owner.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mudH38FU1L34mEeNJNgbB999e97f -
feat(editor): fold the live-site editor into the Studio as a mounted app
The editor ships as a package mounted at /api/editor rather than merged in: one implementation runs standalone on a single-site box and here as a Studio tab, and the prefix settles the /healthz and /api/media collisions for free. core/dbmesh.py now reads and writes the editor's sites.json on the mounted volume. That registry was in-memory only, so a Space restart silently dropped every mesh; it now survives a redeploy, and a database registered in either face is both editable and migratable. healthz no longer 500s on a Space with nothing registered yet — liveness must not depend on configuration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mudH38FU1L34mEeNJNgbBc05106c