@north-light/cli 0.3.580 → 0.3.581
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +6 -1
- package/dist/api/client.js +3 -3
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/execution-start.d.ts +14 -0
- package/dist/api/dto/health.d.ts +20 -0
- package/dist/api/dto/imports.d.ts +142 -0
- package/dist/api/dto/imports.js +0 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -1
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.js +1 -1
- package/dist/clients/attach/session/connection.d.ts +3 -1
- package/dist/clients/attach/session/connection.js +1 -1
- package/dist/clients/attach/viewer.js +510 -510
- package/dist/commands/api-client.d.ts +7 -4
- package/dist/commands/api-client.js +3 -3
- package/dist/commands/sys/daemon.js +1 -1
- package/dist/commands/sys/import/resume.d.ts +1 -0
- package/dist/commands/sys/import/resume.js +1 -0
- package/dist/commands/sys/import/start.d.ts +5 -0
- package/dist/commands/sys/import/start.js +4 -0
- package/dist/commands/sys/join.d.ts +2 -0
- package/dist/commands/sys/join.js +1 -1
- package/dist/commands/sys/machine/rotate.d.ts +1 -0
- package/dist/commands/sys/machine/rotate.js +1 -0
- package/dist/commands/sys/signin.d.ts +7 -0
- package/dist/commands/sys/signin.js +2 -0
- package/dist/commands/sys.js +1 -1
- package/dist/core/blobs/r2.d.ts +4 -0
- package/dist/core/blobs/r2.js +7 -4
- package/dist/core/canvas/daemon-owner.d.ts +1 -1
- package/dist/core/canvas/executions.d.ts +1 -1
- package/dist/core/canvas/node-tokens.d.ts +2 -0
- package/dist/core/canvas/node-tokens.js +3 -3
- package/dist/core/directory/person-credential.d.ts +36 -0
- package/dist/core/directory/person-credential.js +1 -0
- package/dist/core/graph/deletion.d.ts +4 -1
- package/dist/core/graph/deletion.js +3 -3
- package/dist/core/graph/documents.d.ts +4 -0
- package/dist/core/graph/documents.js +10 -10
- package/dist/core/graph/reader.js +2 -2
- package/dist/core/import/bundle/blob-keys.d.ts +10 -0
- package/dist/core/import/bundle/blob-keys.js +1 -0
- package/dist/core/import/bundle/layout.d.ts +89 -0
- package/dist/core/import/bundle/layout.js +5 -0
- package/dist/core/import/export/archive.d.ts +11 -0
- package/dist/core/import/export/archive.js +1 -0
- package/dist/core/import/export/cloud.d.ts +21 -0
- package/dist/core/import/export/cloud.js +1 -0
- package/dist/core/import/export/dump.d.ts +34 -0
- package/dist/core/import/export/dump.js +3 -0
- package/dist/core/import/export/files.d.ts +22 -0
- package/dist/core/import/export/files.js +1 -0
- package/dist/core/import/export/inline.d.ts +3 -0
- package/dist/core/import/export/inline.js +1 -0
- package/dist/core/import/export/source.d.ts +2 -0
- package/dist/core/import/export/source.js +1 -0
- package/dist/core/import/export/stores.d.ts +9 -0
- package/dist/core/import/export/stores.js +1 -0
- package/dist/core/import/load/bind.d.ts +19 -0
- package/dist/core/import/load/bind.js +15 -0
- package/dist/core/import/load/blobs.d.ts +10 -0
- package/dist/core/import/load/blobs.js +1 -0
- package/dist/core/import/load/catalog.d.ts +32 -0
- package/dist/core/import/load/catalog.js +9 -0
- package/dist/core/import/load/copy.d.ts +26 -0
- package/dist/core/import/load/copy.js +6 -0
- package/dist/core/import/load/path-map.d.ts +7 -0
- package/dist/core/import/load/path-map.js +1 -0
- package/dist/core/import/load/prepare.d.ts +37 -0
- package/dist/core/import/load/prepare.js +10 -0
- package/dist/core/import/load/rewrite.d.ts +39 -0
- package/dist/core/import/load/rewrite.js +9 -0
- package/dist/core/import/load/rollback.d.ts +3 -0
- package/dist/core/import/load/rollback.js +6 -0
- package/dist/core/import/load/run.d.ts +22 -0
- package/dist/core/import/load/run.js +3 -0
- package/dist/core/import/load/stage.d.ts +15 -0
- package/dist/core/import/load/stage.js +1 -0
- package/dist/core/import/load/stores.d.ts +24 -0
- package/dist/core/import/load/stores.js +2 -0
- package/dist/core/import/load/verify.d.ts +4 -0
- package/dist/core/import/load/verify.js +11 -0
- package/dist/core/runtime/broker.js +1 -1
- package/dist/core/runtime/revive.d.ts +5 -2
- package/dist/core/runtime/revive.js +2 -2
- package/dist/core/runtime/runner/blaxel/runner.js +3 -3
- package/dist/core/runtime/runner/index.d.ts +1 -0
- package/dist/core/runtime/runner/index.js +1 -1
- package/dist/core/runtime/runner/machine/call.d.ts +8 -0
- package/dist/core/runtime/runner/machine/call.js +3 -0
- package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
- package/dist/core/runtime/runner/machine/environment.js +1 -0
- package/dist/core/runtime/runner/machine/executor.d.ts +23 -0
- package/dist/core/runtime/runner/machine/executor.js +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
- package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
- package/dist/core/runtime/runner/machine/ops.js +1 -0
- package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
- package/dist/core/runtime/runner/machine/runner.js +1 -0
- package/dist/core/runtime/runner/types.d.ts +7 -1
- package/dist/core/runtime/warm-pool.d.ts +2 -0
- package/dist/core/runtime/warm-pool.js +1 -1
- package/dist/core/secrets/env-rows.d.ts +1 -0
- package/dist/core/secrets/env-rows.js +1 -1
- package/dist/core/storage/purge.d.ts +6 -0
- package/dist/core/storage/purge.js +4 -0
- package/dist/core/subscription-state.d.ts +1 -0
- package/dist/core/subscription-state.js +1 -1
- package/dist/daemon/api/bridge.js +3 -3
- package/dist/daemon/api/handlers/attach.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/daemon.js +1 -1
- package/dist/daemon/api/handlers/execution-start.js +2 -2
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/imports.d.ts +2 -0
- package/dist/daemon/api/handlers/imports.js +1 -0
- package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
- package/dist/daemon/api/handlers/machine-credential.js +1 -0
- package/dist/daemon/api/handlers/profiles.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/handlers/runs.js +1 -1
- package/dist/daemon/api/import-auth.d.ts +6 -0
- package/dist/daemon/api/import-auth.js +1 -0
- package/dist/daemon/api/operations.d.ts +2 -2
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/router.d.ts +7 -1
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/broker-signals/bus.d.ts +3 -2
- package/dist/daemon/broker-signals/bus.js +1 -1
- package/dist/daemon/cron/armed-context.d.ts +1 -0
- package/dist/daemon/cron/armed-context.js +1 -1
- package/dist/daemon/cron/closed.d.ts +31 -0
- package/dist/daemon/cron/closed.js +1 -0
- package/dist/daemon/cron/ended.js +2 -2
- package/dist/daemon/cron/lease-recovery.js +1 -1
- package/dist/daemon/cron/machine-exec.d.ts +5 -0
- package/dist/daemon/cron/machine-exec.js +1 -0
- package/dist/daemon/cron/machine-fire.d.ts +26 -0
- package/dist/daemon/cron/machine-fire.js +1 -0
- package/dist/daemon/cron/recurrence.d.ts +14 -0
- package/dist/daemon/cron/recurrence.js +1 -0
- package/dist/daemon/cron/run-token.d.ts +7 -0
- package/dist/daemon/cron/run-token.js +1 -0
- package/dist/daemon/cron-run.js +2 -2
- package/dist/daemon/crtrd-cli.js +1 -1
- package/dist/daemon/crtrd.d.ts +2 -2
- package/dist/daemon/crtrd.js +10 -10
- package/dist/daemon/drain.d.ts +25 -0
- package/dist/daemon/drain.js +9 -0
- package/dist/daemon/due-sources.d.ts +1 -1
- package/dist/daemon/due-sources.js +1 -1
- package/dist/daemon/due.js +8 -8
- package/dist/daemon/fleet.js +5 -5
- package/dist/daemon/import-dump-run.d.ts +1 -0
- package/dist/daemon/import-dump-run.js +5 -0
- package/dist/daemon/machine-daemon.d.ts +7 -0
- package/dist/daemon/machine-daemon.js +1 -0
- package/dist/daemon/machine-link/client.d.ts +29 -0
- package/dist/daemon/machine-link/client.js +1 -0
- package/dist/daemon/machine-link/config.d.ts +14 -0
- package/dist/daemon/machine-link/config.js +3 -0
- package/dist/daemon/machine-link/credential.d.ts +7 -0
- package/dist/daemon/machine-link/credential.js +2 -0
- package/dist/daemon/machine-link/forward.d.ts +16 -0
- package/dist/daemon/machine-link/forward.js +1 -0
- package/dist/daemon/machine-link/frames.d.ts +49 -0
- package/dist/daemon/machine-link/frames.js +1 -0
- package/dist/daemon/machine-link/holder.d.ts +73 -0
- package/dist/daemon/machine-link/holder.js +9 -0
- package/dist/daemon/manage.d.ts +19 -3
- package/dist/daemon/manage.js +3 -3
- package/dist/daemon/reconcilers/cron-lane.js +1 -1
- package/dist/daemon/reconcilers/delivery-lane.js +2 -2
- package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
- package/dist/daemon/reconcilers/import-lane.js +1 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
- package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
- package/dist/daemon/reconcilers/purge-due-work.js +1 -0
- package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
- package/dist/daemon/reconcilers/row-claim.js +2 -2
- package/dist/daemon/runs/observer.js +3 -3
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
- package/scripts/sandbox-start.mjs +185 -2
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
var se=Object.defineProperty;var s=(
|
|
1
|
+
var se=Object.defineProperty;var s=(t,n)=>se(t,"name",{value:n,configurable:!0});import{randomUUID as ce}from"node:crypto";import{CrtrError as R,notFound as N,usage as l}from"../errors.js";import{ExitCode as g}from"../../types.js";import{get as ue,getMany as we,put as B}from"./bodies.js";import{dropUnresolved as pe,linksOf as le,recordActedOn as O,setLinks as G,setUnresolved as de,unresolvedLinksCouldName as me}from"./edges.js";import{publish as j}from"./events.js";import{readableOrDeleted as fe,resolveName as ye}from"./names.js";import{createObject as he,getById as m}from"./objects.js";import{liveLookups as _e,readerForNode as Ee,readerForObject as ve}from"./reader.js";import{replaceRules as q,setRulesOwner as Re}from"./rules.js";import{recordWatch as x}from"./watches.js";import{withCanvasWrite as y}from"../canvas/db.js";import{PURGE_RETENTION_MS as Ne}from"../storage/purge.js";import{filterAsync as ge,forEachAsync as Oe,mapAsync as A}from"../async-array.js";import{check as L,resolveReader as Se,userOf as K}from"../permissions/evaluator.js";import{missingMemoryScope as Y}from"../permissions/ceiling.js";import{deleteFact as De,parseSubject as Te,writeActsFor as ke,writeFact as je}from"../permissions/facts.js";import{PermissionError as xe}from"../permissions/errors.js";function _(t){return t.class==="node"?t.node:t.class==="app"?t.app:t.class==="object"?t.object:t.person}s(_,"actorOf");async function M(t,n){const e=`user:${await K(t,n)}`;return n.class==="person"?{app:null,for:e}:{app:(await t.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(n.app.id))?.external_id??null,for:e}}s(M,"actingOf");function Ae(t,n){return new R("conflict",t,g.GENERAL,n)}s(Ae,"conflict");function h(t){if(t.type!=="document")throw l(`${t.type} ${t.id} is not a document`)}s(h,"assertDocument");async function V(t,n){return(await t.prepare(`WITH RECURSIVE chain(id, owner, type, depth) AS (
|
|
2
2
|
SELECT id, owner, type, 0 FROM objects WHERE id = ?
|
|
3
3
|
UNION ALL
|
|
4
4
|
SELECT o.id, o.owner, o.type, c.depth + 1 FROM objects o JOIN chain c ON o.id = c.owner WHERE c.depth < 12
|
|
5
|
-
) SELECT type FROM chain ORDER BY depth DESC LIMIT 1`).get(n.id))?.type??n.type}s(V,"spaceType");async function
|
|
6
|
-
WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rev_id);return{rev_id:n.rev_id,seq:n.seq,number:n.number,body_hash:n.body_hash,author:n.author,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,acting_app:n.acting_app,acting_for:n.acting_for,parents:
|
|
7
|
-
AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id) ORDER BY r.number DESC, r.rev_id`).all(n.id);return await
|
|
8
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.id,
|
|
5
|
+
) SELECT type FROM chain ORDER BY depth DESC LIMIT 1`).get(n.id))?.type??n.type}s(V,"spaceType");async function C(t,n,e){if(e!=="public"&&await V(t,n)==="repo")throw l(`a repo owns only public documents; ${n.name} cannot own a ${e} one`,{rule:"repo-public-only",next:"Write it as public, or give it another owner (your own documents can carry `if: {repo: <handle>}`)."})}s(C,"assertPublicInRepo");async function U(t,n,e,a){if(e.trim()===""||e!==e.trim())throw l(`invalid document name ${JSON.stringify(e)}`);const i=await t.prepare("SELECT id FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(n.id,e);if(i&&i.id!==a?.id)throw Ae(`${n.name} already has a document named ${e}`,{name:e,holder:i.id})}s(U,"assertNameFree");async function W(t,n,e){if(!await L(t,n,"read",e.id))throw N(`no document ${e.id}`)}s(W,"readable");function X(t){return new R("scope_missing",`missing scope ${t}`,g.GENERAL,{scope:t})}s(X,"scopeMissing");async function S(t,n,e){await W(t,n,e);const{source_path:a}=await v(t,e);if(a!==null)throw new R("forbidden",`${e.name} is a built-in document shipped by a package; \`crtr doc\` cannot change it`,g.GENERAL,{id:e.id,next:"Edit it in the source repository of the package that ships it (for crtr's own documents, the crouter repo), not with `crtr doc`."});const i=await Y(t,n,e.id,"rw");if(i!==null)throw X(i);if(!await L(t,n,"edit",e.id))throw new R("forbidden",`you cannot change ${e.name}`,g.GENERAL,{id:e.id})}s(S,"changeable");async function z(t,n){const e=await t.prepare(`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rev_id = rp.parent
|
|
6
|
+
WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rev_id);return{rev_id:n.rev_id,seq:n.seq,number:n.number,body_hash:n.body_hash,author:n.author,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,acting_app:n.acting_app,acting_for:n.acting_for,parents:e.map(a=>a.rev_id)}}s(z,"toRevision");async function I(t,n){const e=await t.prepare(`SELECT * FROM revisions r WHERE r.document = ?
|
|
7
|
+
AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id) ORDER BY r.number DESC, r.rev_id`).all(n.id);return await A(e,async a=>await z(t,a))}s(I,"heads");function Le(t){return t.map(n=>n.rev_id).sort().join(",")}s(Le,"headsKeyOf");async function v(t,n){const e=await t.prepare("SELECT * FROM documents WHERE object = ?").get(n.id);if(!e)throw N(`no document record for ${n.id}`);return{summary:e.summary,heads_key:e.heads_key,revision:e.revision,why_it_exists:e.why_it_exists,unlisted:e.unlisted===1,lint_ignore:e.lint_ignore_json===null?null:JSON.parse(e.lint_ignore_json),extensions:e.extensions_json===null?null:JSON.parse(e.extensions_json),source_path:e.source_path}}s(v,"documentRecord");async function D(t,n,e){const a=await t.prepare(`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at, acting_app, acting_for)
|
|
8
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.id,e.rev_id??ce(),e.number,e.body_hash,e.author?.id??null,e.rationale,e.verbatim?1:0,e.created_at??new Date().toISOString(),e.acting?.app??null,e.acting?.for??null),i=t.prepare("INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const r of e.parents)await i.run(a.rev_id,r);return a}s(D,"insertRevision");function Ie(t){const n=new Set;for(const e of t.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(e[1].trim());return[...n]}s(Ie,"linksIn");async function Q(t,n,e){const a=[],i=[];for(const r of e){const o=await ye(t,n,r);"object"in o?a.push({written_as:r,dst:o.object}):"deleted"in o?a.push({written_as:r,dst:o.deleted}):"remote"in o||i.push(r)}return{links:a,unresolved:i}}s(Q,"resolveLinks");async function Z(t,n,e,a){const{links:i,unresolved:r}=await Q(t,n,Ie(a));return await ie(t,await G(t,e,i)),await de(t,e,r),r}s(Z,"setRevisionLinks");async function F(t,n,e){const a=new Map;for(const{src:i,name:r}of await me(t,n.name,n.id))a.set(i,[...a.get(i)??[],r]);for(const[i,r]of a){const o=await m(t,i),c=o?.owner==null?null:await m(t,o.owner);if(!o||o.deleted_at||o.home_machine!==null||!c||c.deleted_at)continue;const p=c.type==="node"?await Ee(t,c.id,e??(async()=>[]),_e(t),!0):await ve(t,o,e),u=(await Q(t,p,r)).links.filter(d=>d.dst.id===n.id);u.length!==0&&(await G(t,o,[...await le(t,o),...u]),await pe(t,o,u.map(d=>d.written_as)))}}s(F,"linkNewlyNamed");async function $e(t,n,e){return n.type==="node"?n.id:await t.prepare(`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.id
|
|
9
9
|
WHERE o.name = ? AND o.type <> ? AND o.type IN ('person','app','repo','profile')
|
|
10
|
-
AND d.type = 'document' AND d.name = ? AND d.deleted_at IS NULL LIMIT 1`).get(n.name,n.type,
|
|
11
|
-
WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.id);if(a.length===0)return
|
|
12
|
-
WHERE e.type = 'links-to' AND e.src = ? AND (${p.sql}) ORDER BY e.written_as`).all(
|
|
13
|
-
WHERE (resource_type = ? AND resource_id = ?) OR (subject_type = ? AND subject_id = ?)`).all(n.schema_type,n.id,n.schema_type,n.id);for(const a of
|
|
10
|
+
AND d.type = 'document' AND d.name = ? AND d.deleted_at IS NULL LIMIT 1`).get(n.name,n.type,e)?`${n.type}:${n.name}`:n.name}s($e,"ownerPrefix");async function b(t,n,e){const a=await t.prepare(`SELECT e.written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
|
|
11
|
+
WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.id);if(a.length===0)return e;const i=new Map;for(const r of a){if(r.deleted_at||r.written_as===r.id)continue;const o=!r.written_as.includes("/");if(o&&r.name===r.written_as)continue;const c=r.owner===null?null:await m(t,r.owner),p=c?`${await $e(t,c,r.name)}/${r.name}`:r.name,u=o&&r.owner===n.owner?r.name:p;u!==r.written_as&&i.set(r.written_as,u)}return i.size===0?e:e.replace(/\[\[([^[\]\n]+?)\]\]/g,(r,o)=>{const c=i.get(o.trim());return c===void 0?r:`[[${c}]]`})}s(b,"withCurrentNames");async function J(t,n,e){if(e.preview!==void 0){if(e.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});await t.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(e.preview,n.id)}e.privacy!==void 0&&(await C(t,await m(t,n.owner),e.privacy),await t.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(e.privacy,n.id));const a=s(async(r,o)=>await t.prepareUncached(`UPDATE documents SET ${r} = ? WHERE object = ?`).run(o,n.id),"set");e.summary!==void 0&&await a("summary",e.summary),e.whyItExists!==void 0&&await a("why_it_exists",e.whyItExists),e.unlisted!==void 0&&await a("unlisted",e.unlisted?1:0),e.lintIgnore!==void 0&&await a("lint_ignore_json",e.lintIgnore===null?null:JSON.stringify(e.lintIgnore)),e.extensions!==void 0&&await a("extensions_json",e.extensions===null?null:JSON.stringify(e.extensions));const i=await m(t,n.id);return e.rules!==void 0&&await q(t,i,e.rules),i}s(J,"applyDocumentFields");async function ee(t,n,e,a="crtr"){if(n.owner.deleted_at)throw l(`${n.owner.name} is deleted`);if(n.relations?.length&&n.schemaType===void 0)throw new xe(400,"schema_type_required","relations need a schemaType: they are facts about a typed resource",{field:"relations"});if(n.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});await C(t,n.owner,n.privacy),await U(t,n.owner,n.name);const i=await he(t,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.id,privacy:n.privacy,name:n.name,preview:n.preview,...n.schemaType!==void 0?{schema_type:n.schemaType}:{}});for(const{relation:o,subject:c}of n.relations??[])await je(t,{resourceType:n.schemaType,resourceId:i.id,relation:o,subject:Te(c),writtenBy:a});await t.prepare("INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(i.id);const r=await J(t,i,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]});return await F(t,r,e),r}s(ee,"createDocument");async function $(t,n){const e=await I(t,n),a=Le(e),i=Math.max(0,...e.map(r=>r.number));return await t.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(a,i,n.id),{heads_key:a,revision:i,headCount:e.length}}s($,"setHeads");async function rt(t,n,e,a,i){return await y(async()=>{const r=await B(t,a),o=_(n),c=await Y(t,n,e.owner.id,"rw",!0);if(c!==null)throw X(c);const p=await M(t,n),u=await ee(t,e,i,p.app??p.for);e.owner.type==="app"&&e.schemaType===void 0&&await ke(t,u.id,await K(t,n));const d=await D(t,u,{number:1,body_hash:r,author:o,rationale:null,parents:[],acting:p});if(!await L(t,n,"edit",u.id))throw N(`no owner ${e.owner.name} you can write to`);const{heads_key:w}=await $(t,u),f=await Z(t,n,u,a);return await O(t,o,u),await x(t,o,u,"write",{readHeadsKey:w}),await j(t,{event:"created",object:u,actor:o,attrs:{revision:1},pointer:d.rev_id},i),{object:u,revision:1,unresolved:f}},{savepoint:!0})}s(rt,"writeDocument");async function He(t,n,e){return(await t.prepare("SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(_(n).id,e.id))?.read_heads_key??null}s(He,"readBase");function Me(t,n){let e=t;for(const[a,{old:i,new:r}]of n.entries()){if(i==="")throw l(`block ${a+1}: a replacement needs non-empty old text`,{field:"replace"});const o=e.indexOf(i);if(o<0)throw l(`block ${a+1}: old text not found: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});if(e.indexOf(i,o+1)>=0)throw l(`block ${a+1}: old text occurs more than once: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});e=e.slice(0,o)+r+e.slice(o+i.length)}return e}s(Me,"applyReplacements");async function ot(t,n,e,a,i,r,o={},c){if(h(e),await S(t,n,e),e.deleted_at)throw l(`${e.name} is deleted`);if(r===null)throw new R("read_first",`read ${e.name} before editing it`,g.GENERAL,{id:e.id});if(i.trim()==="")throw l("an edit needs a rationale",{field:"rationale"});const p=await v(t,e);if(p.heads_key!==r)return{stale:{heads_key:p.heads_key,revision:p.revision}};const u=await I(t,e);let d;if("body"in a)d=a.body;else{if(u.length!==1)throw l(`${e.name} has ${u.length} heads and needs a merge; pass the whole merged body`,{heads:u.length});d=Me(await b(t,e,await ue(t,u[0].body_hash)),a.replace)}try{return await y(async()=>{const w=await B(t,d),f=_(n),T=1+Math.max(0,...u.map(E=>E.number)),P=await D(t,e,{number:T,body_hash:w,author:f,rationale:i,verbatim:a.verbatim===!0,parents:u.map(E=>E.rev_id),acting:await M(t,n)}),H=P.rev_id,re=await t.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(H,T,e.id,r);if(Number(re.changes)===0){const E=await v(t,e);throw new te(E.heads_key,E.revision)}const k=await J(t,e,o),oe=await Z(t,n,k,d);return await O(t,f,k),await x(t,f,k,"write",{readHeadsKey:H}),await j(t,{event:"edited",object:k,actor:f,pointer:P.rev_id,attrs:{revision:T,from_revision:p.revision,rationale:i}},c),{revision:T,heads_key:H,unresolved:oe}},{savepoint:!0})}catch(w){if(w instanceof te)return{stale:{heads_key:w.heads_key,revision:w.revision}};throw w}}s(ot,"editDocument");class te extends Error{static{s(this,"StaleEdit")}heads_key;revision;constructor(n,e){super("stale"),this.heads_key=n,this.revision=e}}async function st(t,n,e,a,i){if(h(e),n&&(await S(t,n,e),i===void 0||i.trim()===""))throw l("an edit needs a rationale",{field:"rationale"});return await y(async()=>{const r=await J(t,e,a);if(n){const o=_(n),c=await v(t,e),p=await He(t,n,e),u=await I(t,e),d=u[0];await D(t,e,{number:1+Math.max(...u.map(f=>f.number)),body_hash:d.body_hash,author:o,rationale:i,parents:[d.rev_id],acting:await M(t,n)});const{heads_key:w}=await $(t,e);await O(t,o,r),await x(t,o,e,"write",p===c.heads_key?{readHeadsKey:w}:{})}return r},{savepoint:!0})}s(st,"setDocumentRecord");async function Ce(t,n,e,a){const i=_(n),{heads_key:r}=await v(t,e);await x(t,i,e,"read",{readHeadsKey:r}),await j(t,{event:"read",object:e,actor:i,attrs:{}},a)}s(Ce,"recordRead");async function ct(t,n,e,a){return h(e),await W(t,n,e),await y(async()=>{const i=await v(t,e),r=await I(t,e),o=await we(t,r.map(w=>w.body_hash)),c=await A(r,async w=>({...w,body:await b(t,e,o.get(w.body_hash))})),p=await fe(t,await Se(t,n),"o"),d=(await t.prepareUncached(`SELECT e.written_as AS _written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
|
|
12
|
+
WHERE e.type = 'links-to' AND e.src = ? AND (${p.sql}) ORDER BY e.written_as`).all(e.id,...p.params)).map(({_written_as:w,...f})=>({written_as:w??"",object:f,deleted:f.deleted_at!==null}));return e.deleted_at||await Ce(t,n,e,a),{object:e,record:i,heads:c,needsMerge:c.length>1,links:d}},{savepoint:!0})}s(ct,"readDocument");async function ut(t,n,e){h(e),await W(t,n,e);const a=await t.prepare("SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, seq DESC").all(e.id);return await A(a,async i=>await z(t,i))}s(ut,"history");async function wt(t,n,e,a,i){if(h(e),e.deleted_at)throw l(`${e.name} is deleted`);return n&&await S(t,n,e),await y(async()=>{const r=a.owner??await m(t,e.owner),o=a.name??e.name;await U(t,r,o,e),await C(t,r,e.privacy),await t.prepare("UPDATE objects SET name = ?, owner = ? WHERE id = ?").run(o,r.id,e.id);const c=await m(t,e.id);if(r.id!==e.owner&&(await Re(t,c),n&&!await L(t,n,"edit",c.id)))throw N(`no owner ${r.name} you can move documents to`);return n&&await O(t,_(n),c),(o!==e.name||r.id!==e.owner)&&await F(t,c,i),c},{savepoint:!0})}s(wt,"moveDocument");async function pt(t,n,e,a,i){return h(e),e.deleted_at?e:(n&&await S(t,n,e),await y(async()=>{const r=await ne(t,n,e,a,i);return await Ue(t,e),await ie(t,await ae(t,e.id),a),r},{savepoint:!0}))}s(pt,"deleteDocument");async function Ue(t,n){if(n.schema_type==null)return;const e=await t.prepare(`SELECT resource_type, resource_id, relation, subject_type, subject_id, subject_relation FROM relations
|
|
13
|
+
WHERE (resource_type = ? AND resource_id = ?) OR (subject_type = ? AND subject_id = ?)`).all(n.schema_type,n.id,n.schema_type,n.id);for(const a of e)await De(t,{resourceType:a.resource_type,resourceId:a.resource_id,relation:a.relation,subject:{type:a.subject_type,id:a.subject_id,relation:a.subject_relation}})}s(Ue,"deleteFactsOf");async function We(t,n,e,a){return h(n),n.deleted_at?await y(async()=>{await U(t,await m(t,n.owner),e,n),await t.prepare("UPDATE objects SET deleted_at = NULL, name = ? WHERE id = ?").run(e,n.id);const i=await m(t,n.id);return await F(t,i,a),i},{savepoint:!0}):n}s(We,"restoreDocument");async function lt(t,n,e,a=Date.now()){if(h(e),!e.deleted_at)return e;if(Date.parse(e.deleted_at)<a-Ne)throw l(`${e.name} was deleted over 30 days ago and can no longer be undeleted`,{id:e.id,deleted_at:e.deleted_at});const i=e.owner===null?null:await m(t,e.owner);if(i===null||i.deleted_at)throw l(`${e.name} cannot be undeleted while its owner is deleted`,{id:e.id});return await y(async()=>{const r=await We(t,e,e.name);return n&&await S(t,n,r),r},{savepoint:!0})}s(lt,"undeleteDocument");async function ne(t,n,e,a,i){await t.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(i??new Date().toISOString(),e.id);const r=await m(t,e.id),o=n?_(n):null;return o&&await O(t,o,r),await j(t,{event:"deleted",object:r,actor:o,attrs:{}},a),await t.prepare("DELETE FROM watches WHERE object = ?").run(e.id),await q(t,r,[]),r}s(ne,"markDeleted");async function ae(t,n){return(await t.prepare("SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n)).map(({dst:e})=>e)}s(ae,"linkTargets");async function ie(t,n,e){const a=t.prepare(`SELECT 1 FROM objects d JOIN objects w ON w.id = d.owner
|
|
14
14
|
WHERE d.id = ? AND d.type = 'document' AND d.deleted_at IS NULL AND w.deleted_at IS NOT NULL
|
|
15
15
|
AND NOT EXISTS (SELECT 1 FROM edges e JOIN objects s ON s.id = e.src
|
|
16
|
-
WHERE e.type = 'links-to' AND e.dst = d.id AND e.src != d.id AND s.deleted_at IS NULL)`),i=[...n];for(;i.length>0;){const r=i.pop();await a.get(r)!==void 0&&(await ne(
|
|
16
|
+
WHERE e.type = 'links-to' AND e.dst = d.id AND e.src != d.id AND s.deleted_at IS NULL)`),i=[...n];for(;i.length>0;){const r=i.pop();await a.get(r)!==void 0&&(await ne(t,null,await m(t,r),e),i.push(...await ae(t,r)))}}s(ie,"collectUnlinked");async function dt(t,n,e){if(n.revisions.length===0)throw l(`import of ${n.name}: no revisions`);return await y(async()=>{const a=await ee(t,n,e);await t.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.revisions[0].created_at,a.id);let i=null;return await Oe(n.revisions,async(r,o)=>{i=(await D(t,a,{...r,number:o+1,parents:i===null?[]:[i]})).rev_id}),await $(t,a),await m(t,a.id)},{savepoint:!0})}s(dt,"importDocument");async function mt(t,n,e){if(h(n),n.privacy!=="public"&&await V(t,n)==="repo")throw l(`a repo owns only public documents; refusing revisions of ${n.name}`);return await y(async()=>{const a=t.prepare("SELECT rev_id, number, document FROM revisions WHERE rev_id = ?");let i=await ge(e,async c=>!await a.get(c.rev_id));for(;i.length>0;){const c=[];for(const p of i){const u=await A(p.parents,async w=>await a.get(w));if(u.some(w=>w===void 0)){c.push(p);continue}const d=u;if(d.some(w=>w.document!==n.id))throw l(`revision ${p.rev_id} names a parent of another document`);await D(t,n,{...p,author:null,number:1+Math.max(0,...d.map(w=>w.number)),parents:d.map(w=>w.rev_id)})}if(c.length===i.length)throw l(`revisions of ${n.name} name unknown parents: ${c.map(p=>p.rev_id).join(", ")}`);i=c}const{heads_key:r,headCount:o}=await $(t,n);return{heads_key:r,headCount:o}},{savepoint:!0})}s(mt,"insertImportedRevisions");async function ft(t,n){const e=await m(t,n);if(!e||e.type!=="document")throw N(`no document ${n}`);return e}s(ft,"documentById");export{_ as actorOf,J as applyDocumentFields,Me as applyReplacements,ie as collectUnlinked,pt as deleteDocument,ft as documentById,v as documentRecord,ot as editDocument,I as heads,ut as history,dt as importDocument,mt as insertImportedRevisions,Ie as linksIn,wt as moveDocument,He as readBase,ct as readDocument,Ce as recordRead,We as restoreDocument,st as setDocumentRecord,Z as setRevisionLinks,lt as undeleteDocument,b as withCurrentNames,rt as writeDocument};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var b=Object.defineProperty;var p=(o,e)=>b(o,"name",{value:e,configurable:!0});import{coveredByAny as d,isSandboxedNode as C}from"../../shared/env.js";import{existsSync as S}from"node:fs";import{CrtrError as L}from"../errors.js";import{getCachedProfile as
|
|
2
|
-
sub, grant_id, scopes_json, cwd, profile_id, app FROM nodes WHERE node_id = ?`).get(e);if(!a||!l)throw new Error(`no node ${e}`);const{grantee:c,sub:s,grant_id:i,scopes_json:u,cwd:f,profile_id:g,app:h}=l,m=h===null?null:await r.object(h);if(!m)throw new Error(`node ${e} has no app`);const _=u===null?null:JSON.parse(u);return{class:"node",node:a,app:m,scopes:await x(c,s??"",i,_,r,t),launched:_,cwd:f,...await N(f,g,n,r),profile:g===null?null:await r.profile(g,m)}}p(J,"readerForNode");async function re(o,e,n=async()=>[],r=w(o)){const t=await o.prepare("SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.id);if(t!==void 0)return await k(o,e,t.envelope_json===null?null:JSON.parse(t.envelope_json),r);if(e.type==="node")return await J(o,e.id,n,r,!0);const a=e.owner===null?null:await r.object(e.owner);if(!a)throw new Error(`watcher ${e.id} has no owner`);const c=(a.type==="app"?a:a.type==="profile"&&a.owner!==null?await r.object(a.owner):null)??await o.prepare("SELECT o.* FROM identities i JOIN objects o ON o.id = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??await o.prepare("SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get();if(!c)throw new Error("no terminal app or person object");const s=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(c.id))?.external_id??"app:terminal",i=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(s),u=a.type==="person"?null:i.length===0?r.pluginScopes(s):i.length===1?JSON.parse(i[0].resolved_scopes_json).filter(f=>f.mode==="always").map(f=>R(f.scope)):null;return{class:"object",object:e,node:e,owner:a,app:c,scopes:u,repos:a.type==="repo"?[a]:[]}}p(re,"readerForObject");async function se(o,e,n){const r=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(e.id))?.external_id;if(r===void 0)return null;const t=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(r);return t.length===0?n.pluginScopes(r):t.length>1?null:JSON.parse(t[0].resolved_scopes_json).filter(a=>a.mode==="always").map(a=>R(a.scope))}p(se,"appScopesNow");async function k(o,e,n,r){const t=await j(o);if(n!==null&&n.app===null)return{class:"object",object:e,node:e,owner:t,app:t,scopes:null,repos:[]};const a=n?.app==null?null:await E(o,n.app),l=n?.app==null?null:await r.grantFor(n.app,n.sub),c=n===null||l===null?null:n.node_scopes===null?l.resolved:n.node_scopes.filter(i=>d(l.resolved,i)),s=a??(e.owner===null?t:await r.object(e.owner)??t);return{class:"object",object:e,node:e,owner:s,app:s,scopes:c,launched:n?.node_scopes??null,repos:[],...n?.place==null?{}:{place:n.place}}}p(k,"readerForStub");function w(o){return{grantById:O,grantFor:v,pluginScopes:p(e=>A(e.replace(/^app:/,""))?.scopes??null,"pluginScopes"),object:p(async e=>await T(o,e),"object"),profile:p(async(e,n)=>await G(o,e,n),"profile"),projectPaths:p(e=>C()?[]
|
|
1
|
+
var b=Object.defineProperty;var p=(o,e)=>b(o,"name",{value:e,configurable:!0});import{coveredByAny as d,isSandboxedNode as C}from"../../shared/env.js";import{existsSync as S}from"node:fs";import{CrtrError as L}from"../errors.js";import{getCachedProfile as $}from"../scoped-state/person-store.js";import{parseScope as R}from"@north-light/identity";import{activeGrant as O,grantFor as v}from"../grants/mirror.js";import{ApiError as M}from"../../api/errors.js";import{pluginAppToken as A}from"../secrets.js";import{loadExactProfileManifest as H}from"../profiles/manifest.js";import{ensureOwner as y,getById as T,nodeObject as W,ownerForGrantee as D,profileHandle as P}from"./objects.js";import{personId as B}from"../canvas/person.js";import{warmCheckoutIdentities as F}from"./repo-sync/repos.js";async function N(o,e,n,r){const t=e===null?[]:r.projectPaths(e),a=await n(o),l=new Set(a.map(s=>s.repo.id)),c=[];for(const s of t){if(!S(s))continue;let i;try{i=await n(s)}catch(u){if(u instanceof L&&u.code==="git-lookup-failed")continue;throw u}for(const u of i)l.has(u.repo.id)||(l.add(u.repo.id),c.push(u))}return{repos:a.map(s=>s.repo),checkouts:a,projectRepos:c.map(s=>s.repo),projectCheckouts:c}}p(N,"viewRepos");async function ne(o,e){const n=await o.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e);if(n!==void 0&&(await F(n.cwd),n.profile_id))for(const r of w(o).projectPaths(n.profile_id))S(r)&&await F(r)}p(ne,"warmNodeRepos");async function ae(o,e,n){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:await j(o)};if(e.class==="app")return{class:"app",app:await E(o,e.app),scopes:e.scopes,...e.sub===void 0?{}:{sub:e.sub}};if(e.class==="machine"||e.class==="import")throw new Error(`a ${e.class} token cannot read as a principal`);if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:await j(o)};const c=await v(e.actor.app,e.sub);if(!c)throw new M(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const s=e.actor.node_scopes;return{class:"app",app:await E(o,e.actor.app),place:e.actor.place,launched:s,scopes:s===null?c.resolved:s.filter(i=>d(c.resolved,i))}}if(e.class==="cron"){const c=e.grant?(await O(e.grant))?.resolved??[]:[],s=e.scopes;return{class:"app",app:await E(o,e.app),scopes:s===null?c:s.filter(i=>d(c,i)),launched:s,place:{run:e.origin_run??"",isolated:e.isolated}}}const r=w(o),t=await W(o,e.node),a=await o.prepare("SELECT cwd, profile_id, app FROM nodes WHERE node_id = ?").get(e.node),l=a.app===null?null:await r.object(a.app);if(!l)throw new Error(`node ${e.node} has no app`);return{class:"node",node:t,app:l,scopes:await x(e.app,e.sub,e.grant,e.scopes,r),launched:e.scopes,cwd:a.cwd,...await N(a.cwd,a.profile_id,n,r),profile:a.profile_id===null?null:await r.profile(a.profile_id,l)}}p(ae,"readerFor");async function J(o,e,n,r=w(o),t=!1){const a=await o.prepare("SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),l=await o.prepare(`SELECT (SELECT external_id FROM identities WHERE object = nodes.app) AS grantee,
|
|
2
|
+
sub, grant_id, scopes_json, cwd, profile_id, app FROM nodes WHERE node_id = ?`).get(e);if(!a||!l)throw new Error(`no node ${e}`);const{grantee:c,sub:s,grant_id:i,scopes_json:u,cwd:f,profile_id:g,app:h}=l,m=h===null?null:await r.object(h);if(!m)throw new Error(`node ${e} has no app`);const _=u===null?null:JSON.parse(u);return{class:"node",node:a,app:m,scopes:await x(c,s??"",i,_,r,t),launched:_,cwd:f,...await N(f,g,n,r),profile:g===null?null:await r.profile(g,m)}}p(J,"readerForNode");async function re(o,e,n=async()=>[],r=w(o)){const t=await o.prepare("SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.id);if(t!==void 0)return await k(o,e,t.envelope_json===null?null:JSON.parse(t.envelope_json),r);if(e.type==="node")return await J(o,e.id,n,r,!0);const a=e.owner===null?null:await r.object(e.owner);if(!a)throw new Error(`watcher ${e.id} has no owner`);const c=(a.type==="app"?a:a.type==="profile"&&a.owner!==null?await r.object(a.owner):null)??await o.prepare("SELECT o.* FROM identities i JOIN objects o ON o.id = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??await o.prepare("SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get();if(!c)throw new Error("no terminal app or person object");const s=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(c.id))?.external_id??"app:terminal",i=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(s),u=a.type==="person"?null:i.length===0?r.pluginScopes(s):i.length===1?JSON.parse(i[0].resolved_scopes_json).filter(f=>f.mode==="always").map(f=>R(f.scope)):null;return{class:"object",object:e,node:e,owner:a,app:c,scopes:u,repos:a.type==="repo"?[a]:[]}}p(re,"readerForObject");async function se(o,e,n){const r=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(e.id))?.external_id;if(r===void 0)return null;const t=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(r);return t.length===0?n.pluginScopes(r):t.length>1?null:JSON.parse(t[0].resolved_scopes_json).filter(a=>a.mode==="always").map(a=>R(a.scope))}p(se,"appScopesNow");async function k(o,e,n,r){const t=await j(o);if(n!==null&&n.app===null)return{class:"object",object:e,node:e,owner:t,app:t,scopes:null,repos:[]};const a=n?.app==null?null:await E(o,n.app),l=n?.app==null?null:await r.grantFor(n.app,n.sub),c=n===null||l===null?null:n.node_scopes===null?l.resolved:n.node_scopes.filter(i=>d(l.resolved,i)),s=a??(e.owner===null?t:await r.object(e.owner)??t);return{class:"object",object:e,node:e,owner:s,app:s,scopes:c,launched:n?.node_scopes??null,repos:[],...n?.place==null?{}:{place:n.place}}}p(k,"readerForStub");function w(o){return{grantById:O,grantFor:v,pluginScopes:p(e=>A(e.replace(/^app:/,""))?.scopes??null,"pluginScopes"),object:p(async e=>await T(o,e),"object"),profile:p(async(e,n)=>await G(o,e,n),"profile"),projectPaths:p(e=>C()?[]:$(e)?.manifest.projects.map(n=>n.path)??[],"projectPaths")}}p(w,"liveLookups");function pe(o){const e=w(o),n=new Map,r=p((t,a)=>(n.has(t)||n.set(t,a()),n.get(t)),"memo");return{grantById:p(t=>r(`g\0${t}`,()=>e.grantById(t)),"grantById"),grantFor:p((t,a)=>r(`a\0${t}\0${a}`,()=>e.grantFor(t,a)),"grantFor"),pluginScopes:p(t=>r(`t\0${t}`,()=>e.pluginScopes(t)),"pluginScopes"),object:p(async t=>await r(`o\0${t}`,async()=>await e.object(t)),"object"),profile:p(async(t,a)=>await r(`p\0${t}\0${a.id}`,async()=>await e.profile(t,a)),"profile"),projectPaths:p(t=>r(`j\0${t}`,()=>e.projectPaths(t)),"projectPaths")}}p(pe,"publishLookups");async function x(o,e,n,r,t,a=!1){const l=n?await t.grantById(n):await t.grantFor(o,e),c=l&&l.grantee===o?l.resolved:null;if(c===null){if(a)return null;throw new Error(`the grant of ${o} is not active`)}return r===null?c:r.filter(s=>d(c,s))}p(x,"nodeScopes");async function j(o){return await D(o,B())}p(j,"personObject");async function E(o,e){return await y(o,"app","app",e,e.replace(/^app:/,""))}p(E,"appObject");async function G(o,e,n){return await o.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
|
|
3
3
|
WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??await y(o,"profile","profile",e,P(e,H(e).manifest.name),n)}p(G,"profileObject");export{se as appScopesNow,w as liveLookups,G as profileObject,pe as publishLookups,ae as readerFor,J as readerForNode,re as readerForObject,N as viewRepos,ne as warmNodeRepos};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const runBlobPrefix: (run: string) => string;
|
|
2
|
+
export declare const runBlobKey: (run: string, key: string) => string;
|
|
3
|
+
/** `node`: the owner is a node id of the bundle; `request`: a human request id of the bundle. */
|
|
4
|
+
export interface ParsedBlobKey {
|
|
5
|
+
kind: 'conversation' | 'attachment' | 'job' | 'event' | 'request';
|
|
6
|
+
ownerTable: 'nodes' | 'human_requests';
|
|
7
|
+
owner: string;
|
|
8
|
+
}
|
|
9
|
+
/** The key's kind and owner, or null when it is none of the keys cycle 5 reads (or has an empty, `.` or `..` segment). */
|
|
10
|
+
export declare function parseBlobKey(key: string): ParsedBlobKey | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var r=Object.defineProperty;var n=(t,e)=>r(t,"name",{value:e,configurable:!0});const u=n(t=>`imports/${t}/blobs/`,"runBlobPrefix"),c=n((t,e)=>`${u(t)}${e}`,"runBlobKey"),a=[["conversation",/^conversations\/([^/]+)\/[^/]+\/[^/]+$/],["attachment",/^attachments\/([^/]+)\/[0-9a-f]{64}$/],["job",/^jobs\/([^/]+)\/[^/]+\/output(?:\/\d{8})?$/],["event",/^events\/([^/]+)\/\d{16}-\d{16}-[0-9a-f]{32}\.jsonl\.zst$/],["request",/^requests\/([^/]+)\/.+$/]];function i(t){if(t.split("/").some(e=>e===""||e==="."||e===".."))return null;for(const[e,s]of a){const o=s.exec(t);if(o!==null)return{kind:e,ownerTable:e==="request"?"human_requests":"nodes",owner:o[1]}}return null}n(i,"parseBlobKey");export{i as parseBlobKey,c as runBlobKey,u as runBlobPrefix};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** `bodies` (revision text, design-fleet D106) is copied like any table but dumped apart: its 667 MB do not fit one row file (`encodeRows` builds one string), so its rows
|
|
2
|
+
* travel as the part files named in `source.bodyParts` (see `rowKeys`), each written and read a line at a time (`RowFileWriter`, `rowLines`). */
|
|
3
|
+
export declare const BODIES_TABLE = "bodies";
|
|
4
|
+
/** Tables whose rows are dumped, staged and (after rewrites) copied, in no particular order: the lane computes the copy
|
|
5
|
+
* order from the target's foreign-key catalog. Each row file holds the table's rows in primary-key order (identity-ordered
|
|
6
|
+
* tables in source `seq` order). */
|
|
7
|
+
export declare const COPIED_TABLES: readonly ["objects", "documents", "revisions", "revision_parents", "identities", "jobs", "custom_objects", "context_sessions", "environments", "object_kinds", "edges", "watches", "watch_subscriptions", "exposures", "rules", "unresolved_links", "nodes", "sessions", "worktrees", "node_faults", "waits", "structured_requests", "memory_reads", "deleted_isolated_nodes", "node_outcome_deliveries", "run_events", "messages", "message_causes", "deliveries", "crons", "cron_runs", "daemon_actions", "human_requests", "human_action_deliveries", "review_comments", "review_comment_events", "audit_rows", "app_users", "idempotency_keys", "uploads", "upload_uses", "shares", "provider_approvals", "relations", "charges", "limits", "conversation_parts", "conversation_entries", "profiles", "bodies"];
|
|
8
|
+
/** Dumped and staged so rewrite 2 can map them, never inserted into the target (`grants`: the directory writes them). */
|
|
9
|
+
export declare const STAGED_ONLY_TABLES: readonly ["grants"];
|
|
10
|
+
/** Every table the exporter dumps. */
|
|
11
|
+
export declare const DUMPED_TABLES: readonly string[];
|
|
12
|
+
/** Tables with an identity primary key, tracked in `import_key_map` (source key → target key). */
|
|
13
|
+
export declare const KEY_MAPPED_TABLES: {
|
|
14
|
+
readonly audit_rows: "id";
|
|
15
|
+
readonly review_comment_events: "seq";
|
|
16
|
+
readonly app_users: "uid";
|
|
17
|
+
};
|
|
18
|
+
/** Tables the target numbers by `seq`/identity in source order (the staged `seq` is not carried over). */
|
|
19
|
+
export declare const SEQ_TABLES: readonly ["objects", "revisions", "watches", "watch_subscriptions", "sessions", "waits", "structured_requests", "deliveries", "run_events"];
|
|
20
|
+
export declare const bundleKeys: {
|
|
21
|
+
readonly prefix: (run: string) => string;
|
|
22
|
+
readonly rows: (run: string, table: string) => string;
|
|
23
|
+
readonly source: (run: string) => string;
|
|
24
|
+
readonly manifest: (run: string) => string;
|
|
25
|
+
readonly nodeFiles: (run: string, node: string) => string;
|
|
26
|
+
readonly bodyRows: (run: string, part: string) => string;
|
|
27
|
+
readonly request: (requestId: string, relative: string) => string;
|
|
28
|
+
};
|
|
29
|
+
/** The blob keys of a table's row files: one file, or for `bodies` its parts. */
|
|
30
|
+
export declare function rowKeys(run: string, table: string, source: Pick<SourceJson, 'bodyParts'>): string[];
|
|
31
|
+
/** `imports/<run>/source.json`. */
|
|
32
|
+
export interface SourceJson {
|
|
33
|
+
installId: string;
|
|
34
|
+
kind: 'machine' | 'hosted';
|
|
35
|
+
/** The source's person (`user:<id>`, or the local person on a runtime that never joined). */
|
|
36
|
+
person: string;
|
|
37
|
+
release: string;
|
|
38
|
+
schemaVersion: number;
|
|
39
|
+
/** Rows per dumped table. The lane refuses a row file whose line count differs. */
|
|
40
|
+
counts: Record<string, number>;
|
|
41
|
+
/** The source's machine environment object id, when it has one (`environments.object`). */
|
|
42
|
+
environmentObject?: string;
|
|
43
|
+
/** Hosted sources: every directory the exporter archived for a running node (`nodeFiles` holds its entries as `<from minus leading slash>/<relative path>`).
|
|
44
|
+
* The lane picks the sandbox directory each one lands in (`sandboxPathMap`); the exporter does not. */
|
|
45
|
+
pathPrefixes?: PathPrefix[];
|
|
46
|
+
/** The part files of the `bodies` rows (`imports/<run>/rows/bodies/<part>.jsonl.zst`): the pre-pause dump's parts, then the pause dump's. `counts.bodies` is their total. */
|
|
47
|
+
bodyParts?: string[];
|
|
48
|
+
/** Shipped documents (`documents.source_path` set): document object id → hash of the body text the exporter read from its file; that text is a `bodies` row in the bundle.
|
|
49
|
+
* The lane uses it for a shipped document the target has no copy of, which becomes an ordinary document (I23). */
|
|
50
|
+
shippedBodies?: Record<string, string>;
|
|
51
|
+
}
|
|
52
|
+
/** One archived directory of a running node: `app` is its grantee (`app:<slug>`), `role` says which directory it is. */
|
|
53
|
+
export interface PathPrefix {
|
|
54
|
+
node: string;
|
|
55
|
+
app: string;
|
|
56
|
+
role: 'context' | 'worktree' | 'cwd';
|
|
57
|
+
from: string;
|
|
58
|
+
}
|
|
59
|
+
/** Rewrite 4: source path prefix → target path prefix, applied to the rows of running nodes of a hosted source. */
|
|
60
|
+
export interface PathMapEntry {
|
|
61
|
+
from: string;
|
|
62
|
+
to: string;
|
|
63
|
+
}
|
|
64
|
+
export type PathMap = readonly PathMapEntry[];
|
|
65
|
+
/** Rewrite one path through the map: the longest matching prefix wins; a path under no prefix is returned unchanged. */
|
|
66
|
+
export declare function mapPath(map: PathMap, path: string): string;
|
|
67
|
+
export type RowObject = Record<string, unknown>;
|
|
68
|
+
/** Row-file codec: one JSON object per line, newline-terminated, zstd-compressed. */
|
|
69
|
+
export declare function encodeRows(rows: Iterable<RowObject>): Buffer;
|
|
70
|
+
export declare function decodeRows(data: Buffer | Uint8Array): RowObject[];
|
|
71
|
+
/** One JSON object per line, newline-terminated: the line `RowFileWriter.write` takes. */
|
|
72
|
+
export declare function rowLine(row: RowObject): string;
|
|
73
|
+
/** Writes a row file as a zstd stream, a line at a time, so no string larger than one line exists (`encodeRows` builds the whole file as one string, past V8's
|
|
74
|
+
* ~512 MiB string limit for a large file). `bytes` is the size of the JSON written so far, before compression. */
|
|
75
|
+
export declare class RowFileWriter {
|
|
76
|
+
private readonly zst;
|
|
77
|
+
private readonly finished;
|
|
78
|
+
private written;
|
|
79
|
+
constructor(path: string);
|
|
80
|
+
get bytes(): number;
|
|
81
|
+
/** Write one line (`rowLine`), waiting for the compressor when it is full. */
|
|
82
|
+
write(line: string, size?: number): Promise<void>;
|
|
83
|
+
/** Finish the file; resolves when it is on disk. */
|
|
84
|
+
close(): Promise<void>;
|
|
85
|
+
destroy(): void;
|
|
86
|
+
}
|
|
87
|
+
/** The lines of a zstd row file, decompressed as a stream: each yielded string is one line (without its newline), so memory is one line plus a chunk. A file whose last
|
|
88
|
+
* line is not newline-terminated, or that is not valid zstd, throws when the reader reaches it. */
|
|
89
|
+
export declare function rowLines(data: Buffer | Uint8Array): AsyncGenerator<string>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
var a=Object.defineProperty;var r=(s,e)=>a(s,"name",{value:e,configurable:!0});import{once as d}from"node:events";import{createWriteStream as u}from"node:fs";import{pipeline as f}from"node:stream/promises";import{createZstdCompress as m,createZstdDecompress as p,zstdCompressSync as l,zstdDecompressSync as _}from"node:zlib";const h="bodies",w=["objects","documents","revisions","revision_parents","identities","jobs","custom_objects","context_sessions","environments","object_kinds","edges","watches","watch_subscriptions","exposures","rules","unresolved_links","nodes","sessions","worktrees","node_faults","waits","structured_requests","memory_reads","deleted_isolated_nodes","node_outcome_deliveries","run_events","messages","message_causes","deliveries","crons","cron_runs","daemon_actions","human_requests","human_action_deliveries","review_comments","review_comment_events","audit_rows","app_users","idempotency_keys","uploads","upload_uses","shares","provider_approvals","relations","charges","limits","conversation_parts","conversation_entries","profiles","bodies"],x=["grants"],E=[...w,...x],z={audit_rows:"id",review_comment_events:"seq",app_users:"uid"},$=["objects","revisions","watches","watch_subscriptions","sessions","waits","structured_requests","deliveries","run_events"],c={prefix:r(s=>`imports/${s}/`,"prefix"),rows:r((s,e)=>`imports/${s}/rows/${e}.jsonl.zst`,"rows"),source:r(s=>`imports/${s}/source.json`,"source"),manifest:r(s=>`imports/${s}/manifest.jsonl.zst`,"manifest"),nodeFiles:r((s,e)=>`imports/${s}/node-files/${e}.tar.zst`,"nodeFiles"),bodyRows:r((s,e)=>`imports/${s}/rows/bodies/${e}.jsonl.zst`,"bodyRows"),request:r((s,e)=>`requests/${s}/${e}`,"request")};function B(s,e,t){return e===h?(t.bodyParts??[]).map(o=>c.bodyRows(s,o)):[c.rows(s,e)]}r(B,"rowKeys");function L(s,e){let t;for(const o of s)(e===o.from||e.startsWith(o.from.endsWith("/")?o.from:`${o.from}/`))&&(t===void 0||o.from.length>t.from.length)&&(t=o);return t===void 0?e:t.to+e.slice(t.from.length)}r(L,"mapPath");function j(s){let e="";for(const t of s)e+=`${JSON.stringify(t)}
|
|
2
|
+
`;return l(Buffer.from(e,"utf8"))}r(j,"encodeRows");function A(s){const e=_(s).toString("utf8");if(e==="")return[];if(!e.endsWith(`
|
|
3
|
+
`))throw new Error("row file is truncated: the last line is not newline-terminated");return e.slice(0,-1).split(`
|
|
4
|
+
`).map(t=>JSON.parse(t))}r(A,"decodeRows");function D(s){return`${JSON.stringify(s)}
|
|
5
|
+
`}r(D,"rowLine");class O{static{r(this,"RowFileWriter")}zst=m();finished;written=0;constructor(e){this.finished=f(this.zst,u(e)),this.finished.catch(()=>{})}get bytes(){return this.written}async write(e,t=Buffer.byteLength(e,"utf8")){this.written+=t,this.zst.write(e,"utf8")||await Promise.race([d(this.zst,"drain"),this.finished])}async close(){this.zst.end(),await this.finished}destroy(){this.zst.destroy()}}async function*P(s){const e=p({chunkSize:1048576});e.end(s);let t=[];for await(const o of e){let n=0;for(let i=o.indexOf(10,n);i!==-1;i=o.indexOf(10,n))t.push(o.subarray(n,i)),yield(t.length===1?t[0]:Buffer.concat(t)).toString("utf8"),t=[],n=i+1;n<o.length&&t.push(o.subarray(n))}if(t.length>0)throw new Error("row file is truncated: the last line is not newline-terminated")}r(P,"rowLines");export{h as BODIES_TABLE,w as COPIED_TABLES,E as DUMPED_TABLES,z as KEY_MAPPED_TABLES,O as RowFileWriter,$ as SEQ_TABLES,x as STAGED_ONLY_TABLES,c as bundleKeys,A as decodeRows,j as encodeRows,L as mapPath,B as rowKeys,D as rowLine,P as rowLines};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PathPrefix } from '../bundle/layout.js';
|
|
2
|
+
/** One node's archive and the directories in it: context dir, worktree, and a cwd outside both. The lane picks each directory's sandbox target. */
|
|
3
|
+
export declare function nodeArchive(node: {
|
|
4
|
+
node: string;
|
|
5
|
+
app: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
worktree: string | null;
|
|
8
|
+
}, contextDir?: string): Promise<{
|
|
9
|
+
data: Buffer;
|
|
10
|
+
prefixes: PathPrefix[];
|
|
11
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var a=Object.defineProperty;var s=(r,c)=>a(r,"name",{value:c,configurable:!0});import{existsSync as n}from"node:fs";import{join as l}from"node:path";import{zstdCompressSync as u}from"node:zlib";import*as w from"tar";import{nodeAgentDir as h}from"../../canvas/paths.js";async function S(r,c=l(h(r.node,r.app),"context")){const e={node:r.node,app:r.app},t=[{...e,role:"context",from:c}];r.worktree!==null&&t.push({...e,role:"worktree",from:r.worktree});const f=t.some(o=>r.cwd===o.from||r.cwd.startsWith(`${o.from}/`));r.cwd!==""&&!f&&t.push({...e,role:"cwd",from:r.cwd});const i=[],p=w.create({cwd:"/",portable:!0,filter:s((o,m)=>!("isSymbolicLink"in m&&m.isSymbolicLink()),"filter")},t.filter(o=>n(o.from)).map(o=>o.from.replace(/^\//,"")));for await(const o of p)i.push(o);return{data:u(Buffer.concat(i)),prefixes:t}}s(S,"nodeArchive");export{S as nodeArchive};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ImportRunDTO, ImportStartRequest, ImportStartedDTO, ImportStoreNameDTO, ImportUploadUrlsDTO } from '../../../api/dto/imports.js';
|
|
2
|
+
/** The dump directory's file holding the opened file stores, and the order they are uploaded in. */
|
|
3
|
+
export declare const STORE_FILE = "stores.json";
|
|
4
|
+
export declare const STORE_ORDER: readonly ImportStoreNameDTO[];
|
|
5
|
+
export declare class ImportCloud {
|
|
6
|
+
private readonly apiUrl;
|
|
7
|
+
private readonly token;
|
|
8
|
+
constructor(apiUrl: string, token: string);
|
|
9
|
+
private call;
|
|
10
|
+
start(request: ImportStartRequest): Promise<ImportStartedDTO>;
|
|
11
|
+
uploadUrls(run: string, keys: string[]): Promise<ImportUploadUrlsDTO>;
|
|
12
|
+
putStore(run: string, name: ImportStoreNameDTO, body: unknown): Promise<void>;
|
|
13
|
+
load(run: string): Promise<ImportRunDTO>;
|
|
14
|
+
get(run: string): Promise<ImportRunDTO>;
|
|
15
|
+
machineCredential(run: string): Promise<{
|
|
16
|
+
environment_id: string;
|
|
17
|
+
credential: string;
|
|
18
|
+
}>;
|
|
19
|
+
/** PUT bytes to a signed URL. */
|
|
20
|
+
put(url: string, data: Buffer): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var l=Object.defineProperty;var i=(a,t)=>l(a,"name",{value:t,configurable:!0});import{general as o}from"../../errors.js";const u="stores.json",d=["auth","credentials","env","settings","plugins"];class h{static{i(this,"ImportCloud")}apiUrl;token;constructor(t,e){this.apiUrl=t,this.token=e}async call(t,e,r){const s=await fetch(new URL(e,this.apiUrl),{method:t,headers:{authorization:`Bearer ${this.token}`,...r===void 0?{}:{"content-type":"application/json"}},...r===void 0?{}:{body:JSON.stringify(r)}}),n=await s.text();if(!s.ok)throw o(`${t} ${e} answered ${s.status}: ${n.slice(0,500)}`);return n===""?void 0:JSON.parse(n)}start(t){return this.call("POST","/v1/imports",t)}uploadUrls(t,e){return this.call("POST",`/v1/imports/${t}/upload-urls`,{keys:e})}putStore(t,e,r){return this.call("PUT",`/v1/imports/${t}/stores/${e}`,r)}load(t){return this.call("POST",`/v1/imports/${t}/load`,{})}get(t){return this.call("GET",`/v1/imports/${t}`)}machineCredential(t){return this.call("POST",`/v1/imports/${t}/machine-credential`,{})}async put(t,e){const r=await fetch(t,{method:"PUT",body:new Uint8Array(e)});if(!r.ok)throw o(`upload answered ${r.status}: ${(await r.text()).slice(0,300)}`)}}export{h as ImportCloud,u as STORE_FILE,d as STORE_ORDER};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type SourceJson } from '../bundle/layout.js';
|
|
2
|
+
export interface DumpRequestRef {
|
|
3
|
+
id: string;
|
|
4
|
+
dir: string;
|
|
5
|
+
file: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface DumpRunningNode {
|
|
8
|
+
node: string;
|
|
9
|
+
app: string;
|
|
10
|
+
cwd: string;
|
|
11
|
+
worktree: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface DumpResult {
|
|
14
|
+
source: SourceJson;
|
|
15
|
+
/** Human requests (page and review) with the folder and reviewed file their rows name. */
|
|
16
|
+
requests: DumpRequestRef[];
|
|
17
|
+
/** Nodes with status active or idle, for the hosted node-file archives. */
|
|
18
|
+
runningNodes: DumpRunningNode[];
|
|
19
|
+
/** `body_ref` files that could not be read and stayed as references. */
|
|
20
|
+
bodiesMissing: number;
|
|
21
|
+
bodiesInlined: number;
|
|
22
|
+
}
|
|
23
|
+
/** A `bodies` part file holds about this much JSON (the encoded lines, before compression); a row that alone exceeds it is a part of its own. */
|
|
24
|
+
export declare const PART_JSON_BYTES: number;
|
|
25
|
+
/** A body longer than this many UTF-8 bytes fails the export. The largest body on Silas's Mac is 5.2 MB. JSON escapes one character into at most six (`\u0001`), so a body at
|
|
26
|
+
* the ceiling is a line of at most 192 MiB, well inside V8's ~512 MiB string limit. */
|
|
27
|
+
export declare const BODY_MAX_BYTES: number;
|
|
28
|
+
/** The pre-pause dump of `bodies` (design-import cutover step 2): every row, while nodes still run. Rows are never updated or deleted, so the pause dump
|
|
29
|
+
* needs only the rows whose hash is not in the list this leaves in `<out>/bodies-pre.json`. */
|
|
30
|
+
export declare function dumpBodiesPre(out: string): Promise<{
|
|
31
|
+
parts: string[];
|
|
32
|
+
count: number;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function dumpRows(out: string, kind: SourceJson['kind']): Promise<DumpResult>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var G=Object.defineProperty;var s=(t,n)=>G(t,"name",{value:n,configurable:!0});import{mkdirSync as v,readFileSync as M,writeFileSync as k}from"node:fs";import{join as f}from"node:path";import{openDb as X,withCanvasWrite as K}from"../../canvas/db.js";import{hashOf as Q,storable as Z}from"../../graph/bodies.js";import{personIdAsync as ee}from"../../canvas/person.js";import{CANVAS_SCHEMA_VERSION as te}from"../../canvas/schema-version.js";import{crtrVersion as ne}from"../../../shared/crtr-version.js";import{BODIES_TABLE as x,DUMPED_TABLES as H,RowFileWriter as ae,SEQ_TABLES as re,bundleKeys as se,encodeRows as oe,rowLine as ie}from"../bundle/layout.js";import{parseFrontmatterGeneric as ce}from"../../frontmatter.js";import{inlineMessageBody as le}from"./inline.js";const he=16*1024*1024,pe=8*1024*1024,D=32*1024*1024,W=5e3,$="bodies-pre.json",F=s(t=>`"${t.replace(/"/g,'""')}"`,"q");function de(t){try{const n=M(t,"utf8");return n.trim()===""?null:n}catch(n){if(n.code==="ENOENT")return null;throw n}}s(de,"readText");function ue(t){try{return M(t,"utf8")}catch(n){if(n.code==="ENOENT")return null;throw n}}s(ue,"readFileOrNull");function P(t,n){return new Error(`revision text ${t} is ${n} bytes; the import carries at most ${D} bytes of text per body`)}s(P,"tooLarge");async function q(t,n,p,r,m){const w=f(n,"rows",x);v(w,{recursive:!0});const _=[];let o=null;const S=s(async()=>{const a=o;o=null,a!==null&&await a.close()},"close"),g=s(async a=>{const l=ie(a),i=Buffer.byteLength(l,"utf8");if(o!==null&&o.bytes>0&&o.bytes+i>he&&await S(),o===null){const h=`${p}-${_.length}`;o=new ae(f(w,`${h}.jsonl.zst`)),_.push(h)}await o.write(l,i)},"add");try{for(let a=0;a<r.length;){const l=await t.prepare("SELECT hash, octet_length(text) AS n FROM bodies WHERE hash = ANY(?::text[]) ORDER BY hash").all(r.slice(a,a+W));let i=[],h=0;const R=s(async()=>{if(i.length===0)return;const d=await t.prepare("SELECT hash, text FROM bodies WHERE hash = ANY(?::text[]) ORDER BY hash").all(i);for(const O of d)await g(O);i=[],h=0},"fetchPage");for(const{hash:d,n:O}of l){const E=Number(O);if(E>D)throw P(d,E);i.length>0&&h+E>pe&&await R(),i.push(d),h+=E}await R(),a+=W}for(const a of m){const l=Buffer.byteLength(a.text,"utf8");if(l>D)throw P(a.hash,l);await g(a)}await S()}catch(a){throw o?.destroy(),a}return _}s(q,"writeBodyParts");const J=s(async t=>(await t.prepare("SELECT hash FROM bodies ORDER BY hash").all()).map(n=>n.hash),"allHashes");async function Ne(t){const n=await X(),p=await J(n),r=await q(n,t,"pre",p,[]),m={hashes:p,parts:r,count:p.length};return k(f(t,$),JSON.stringify(m)),{parts:r,count:p.length}}s(Ne,"dumpBodiesPre");function Ee(t){try{return JSON.parse(M(f(t,$),"utf8"))}catch(n){if(n.code==="ENOENT")return{hashes:[],parts:[],count:0};throw n}}s(Ee,"readPre");async function Le(t,n){const p=await ee();return v(f(t,"rows"),{recursive:!0}),K(async r=>{await r.exec("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");const m=new Set((await r.prepareUncached("SELECT table_name FROM information_schema.tables WHERE table_schema = current_schema()").all()).map(e=>e.table_name)),w=H.filter(e=>!m.has(e));if(w.length>0)throw new Error(`the canvas database lacks tables the bundle needs: ${w.join(", ")}`);const _=await r.prepareUncached(`SELECT c.conrelid::regclass::text AS tbl, (SELECT array_agg(a.attname::text ORDER BY k.ord) FROM unnest(c.conkey) WITH ORDINALITY k(attnum, ord)
|
|
2
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS cols FROM pg_constraint c WHERE c.contype = 'p' AND c.connamespace = current_schema()::regnamespace`).all(),o=new Map(_.map(e=>[e.tbl.replace(/"/g,"").replace(/^.*\./,""),e.cols])),S=await r.prepareUncached("SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = current_schema() ORDER BY table_name, ordinal_position").all(),g=new Set(re),a={};let l=0,i=0;for(const e of H.filter(u=>u!==x)){const u=g.has(e)?["seq"]:o.get(e),y=u!==void 0&&u.length>0?u.map(c=>`t.${F(c)}`):S.filter(c=>c.table_name===e).map(c=>`t.${F(c.column_name)}::text`);if(y.length===0)throw new Error(`table ${e} has no columns to order its dump by`);let T=(await r.prepareUncached(`SELECT row_to_json(t)::text AS j FROM ${F(e)} t ORDER BY ${y.join(", ")}`).all()).map(c=>JSON.parse(c.j));e==="messages"&&(T=T.map(c=>{const Y=c.body_ref,j=le(c,de);return typeof Y=="string"&&Y.startsWith("/")&&(j===c?i+=1:l+=1),j})),a[e]=T.length,k(f(t,se.rows("",e).replace(/^imports\/\//,"")),oe(T))}const h=(await r.prepare("SELECT value FROM canvas_meta WHERE key = 'install_id'").get())?.value;if(h===void 0)throw new Error("this canvas database has no install id");const R=await r.prepare("SELECT object FROM environments WHERE kind = 'machine' ORDER BY object LIMIT 1").get(),d={installId:h,kind:n,person:p,release:ne(),schemaVersion:te,counts:a,...R===void 0?{}:{environmentObject:R.object}},O=await r.prepare("SELECT request_id, request_dir, file FROM human_requests ORDER BY request_id").all(),E=await r.prepare("SELECT object, source_path FROM documents WHERE source_path IS NOT NULL ORDER BY object").all(),I={},N=new Map;for(const e of E){const u=ue(e.source_path);if(u===null)continue;const y=Z(ce(u).body),b=Q(y);I[e.object]=b,N.set(b,{hash:b,text:y})}const L=Ee(t),A=new Set(L.hashes),B=(await J(r)).filter(e=>!A.has(e)),C=new Set(B),U=await q(r,t,"pause",B,[...N.values()].filter(e=>!A.has(e.hash)&&!C.has(e.hash))),V=L.count+B.length+[...N.values()].filter(e=>!A.has(e.hash)&&!C.has(e.hash)).length;a[x]=V,d.bodyParts=[...L.parts,...U];const z=await r.prepare(`SELECT n.node_id, r.grantee, n.cwd, w.path AS worktree FROM nodes n JOIN node_rows r ON r.node_id = n.node_id LEFT JOIN worktrees w ON w.node_id = n.node_id AND w.state = 'open'
|
|
3
|
+
WHERE n.status IN ('active', 'idle') ORDER BY n.node_id`).all();return d.shippedBodies=I,{source:d,requests:O.map(e=>({id:e.request_id,dir:e.request_dir,file:e.file})),runningNodes:z.map(e=>({node:e.node_id,app:e.grantee,cwd:e.cwd,worktree:e.worktree})),bodiesInlined:l,bodiesMissing:i}})}s(Le,"dumpRows");export{D as BODY_MAX_BYTES,he as PART_JSON_BYTES,Ne as dumpBodiesPre,Le as dumpRows};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ImportCloud } from './cloud.js';
|
|
2
|
+
export interface LocalObject {
|
|
3
|
+
key: string;
|
|
4
|
+
path: string;
|
|
5
|
+
}
|
|
6
|
+
/** Every file the runtime holds that the cloud serves from a blob key. */
|
|
7
|
+
export declare function localObjects(): LocalObject[];
|
|
8
|
+
/** The reviewed file of a review that lies outside the review's own directory, at the key the lane rewrites `file` to. */
|
|
9
|
+
export declare function reviewSourceObject(requestId: string, requestDir: string, file: string | null): LocalObject | null;
|
|
10
|
+
export declare class Uploader {
|
|
11
|
+
private readonly cloud;
|
|
12
|
+
private readonly run;
|
|
13
|
+
private readonly progress;
|
|
14
|
+
private readonly sent;
|
|
15
|
+
uploaded: number;
|
|
16
|
+
missing: string[];
|
|
17
|
+
constructor(cloud: ImportCloud, run: string, progress: (line: string) => void);
|
|
18
|
+
/** Upload source blobs, each to `imports/<run>/blobs/<its key>`: the upload route signs only under the run's own prefix, and the lane moves each blob to its key. */
|
|
19
|
+
upload(objects: readonly LocalObject[]): Promise<void>;
|
|
20
|
+
/** Upload bytes held in memory (row files, source.json, node archives). */
|
|
21
|
+
uploadBytes(key: string, data: Buffer): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var f=Object.defineProperty;var a=(s,o)=>f(s,"name",{value:o,configurable:!0});import{readFileSync as y,readdirSync as w,statSync as g}from"node:fs";import{basename as b,join as l,relative as k,sep as $}from"node:path";import{runtimeRoot as j}from"../../layout.js";import{bundleKeys as p}from"../bundle/layout.js";import{runBlobKey as d}from"../bundle/blob-keys.js";function c(s,o,t=s){let r;try{r=w(s,{withFileTypes:!0})}catch(e){if(e.code==="ENOENT")return;throw e}for(const e of r){const n=l(s,e.name);e.isDirectory()?c(n,o,t):e.isFile()&&!e.name.endsWith(".tmp")&&!e.name.includes(".tmp-")&&o(n,k(t,n).split($).join("/"))}}a(c,"walk");function S(){const s=j(),o=[];c(l(s,"blobs"),(t,r)=>o.push({key:r,path:t}));for(const t of["requests","reviews"])c(l(s,t),(r,e)=>{const[n,...u]=e.split("/");u.length>0&&o.push({key:p.request(n,u.join("/")),path:r})});return o}a(S,"localObjects");function W(s,o,t){return t===null||!t.startsWith("/")||t.startsWith(o.endsWith("/")?o:`${o}/`)?null:{key:p.request(s,`source/${b(t)}`),path:t}}a(W,"reviewSourceObject");class F{static{a(this,"Uploader")}cloud;run;progress;sent=new Map;uploaded=0;missing=[];constructor(o,t,r){this.cloud=o,this.run=t,this.progress=r}async upload(o){const t=[];for(const r of o){let e;try{e=g(r.path)}catch(u){if(u.code==="ENOENT"){this.missing.push(r.path);continue}throw u}const n=`${e.size}:${e.mtimeMs}`;this.sent.get(r.key)!==n&&t.push({...r,stamp:n})}for(let r=0;r<t.length;r+=100){const e=t.slice(r,r+100),{urls:n}=await this.cloud.uploadUrls(this.run,e.map(i=>d(this.run,i.key)));let u=0;const m=a(async()=>{for(let i=e[u++];i!==void 0;i=e[u++]){const h=n[d(this.run,i.key)];if(h===void 0)throw new Error(`no upload URL returned for ${i.key}`);await this.cloud.put(h,y(i.path)),this.sent.set(i.key,i.stamp),this.uploaded+=1}},"worker");await Promise.all(Array.from({length:Math.min(8,e.length)},m)),this.progress(`uploaded ${Math.min(r+100,t.length)}/${t.length}`)}}async uploadBytes(o,t){const{urls:r}=await this.cloud.uploadUrls(this.run,[o]),e=r[o];if(e===void 0)throw new Error(`no upload URL returned for ${o}`);await this.cloud.put(e,t)}}export{F as Uploader,S as localObjects,W as reviewSourceObject};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var f=Object.defineProperty;var r=(t,e)=>f(t,"name",{value:e,configurable:!0});function i(t,e){const o=t.body_ref;if(typeof o!="string"||!o.startsWith("/"))return t;const s=e(o);if(s===null)return t;const n=t.body_json,y=n!==null&&typeof n=="object"&&!Array.isArray(n)?n:{};return{...t,body_ref:null,body_json:{...y,body_full:s}}}r(i,"inlineMessageBody");export{i as inlineMessageBody};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var o=Object.defineProperty;var e=(t,n)=>o(t,"name",{value:n,configurable:!0});function u(t,n){return{...t,...n.length>0?{pathPrefixes:[...n]}:{}}}e(u,"buildSourceJson");export{u as buildSourceJson};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImportAuthStoreDTO, ImportCredentialsStoreDTO, ImportEnvStoreDTO, ImportPluginsStoreDTO, ImportSettingsStoreDTO } from '../../../api/dto/imports.js';
|
|
2
|
+
export interface ExportedStores {
|
|
3
|
+
auth: ImportAuthStoreDTO;
|
|
4
|
+
credentials: ImportCredentialsStoreDTO;
|
|
5
|
+
env: ImportEnvStoreDTO;
|
|
6
|
+
settings: ImportSettingsStoreDTO;
|
|
7
|
+
plugins: ImportPluginsStoreDTO;
|
|
8
|
+
}
|
|
9
|
+
export declare function readStores(): Promise<ExportedStores>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var k=Object.defineProperty;var _=(a,t)=>k(a,"name",{value:t,configurable:!0});import{openDb as R}from"../../canvas/db.js";import{personIdAsync as h}from"../../canvas/person.js";import{loadSecretsKey as y,open as c}from"../../secrets/index.js";import{aadOf as v}from"../../subscription-state.js";import{aadOf as O}from"../../secrets/env-rows.js";const r=_(a=>a==null?null:String(a),"str"),o=_(a=>Number(a??0),"num");async function M(){const a=await h(),t=await R(),d=y(),u={};for(const e of await t.prepare("SELECT provider_id, credential_sealed, key_id FROM model_api_keys WHERE person = ? ORDER BY provider_id").all(a)){const n=String(e.provider_id);u[n]=JSON.parse(c(e.credential_sealed,String(e.key_id),d,`model_api_keys|${a}|${n}`))}const w=await t.prepare("SELECT provider_id, account_id, family, rate_limited_until, last_rate_limited_at FROM model_account_cooldowns WHERE person = ? ORDER BY family").all(a),E=[];for(const e of await t.prepare("SELECT * FROM model_accounts WHERE person = ? ORDER BY provider_id, position").all(a)){const n=String(e.provider_id),l=String(e.id);if(e.refresh_sealed===null||e.access_sealed===null||e.key_id===null)throw new Error(`managed ${n} account ${l} has no credential record`);const g=String(e.key_id);E.push({provider:n,id:l,label:String(e.label),labelKey:String(e.label_key),position:o(e.position),lastAttemptAt:o(e.last_attempt_at),lastRateLimitedAt:o(e.last_rate_limited_at),rateLimitedUntil:o(e.rate_limited_until),...e.auth_failure==="invalid_grant"?{authFailure:"invalid_grant"}:{},refresh:c(e.refresh_sealed,g,d,v(a,n,l,"refresh")),access:c(e.access_sealed,g,d,v(a,n,l,"access")),expires:o(e.expires),...r(e.vendor_account_id)===null?{}:{accountId:r(e.vendor_account_id)},...r(e.client_id)===null?{}:{clientId:r(e.client_id)},cooldowns:w.filter(s=>s.provider_id===n&&s.account_id===l).map(s=>({family:String(s.family),until:o(s.rate_limited_until),at:o(s.last_rate_limited_at)}))})}const i=await t.prepare("SELECT default_fallback_strength, revert_when_available, openai_device_id FROM model_settings WHERE person = ?").get(a),S={accounts:E,...i===void 0||i.default_fallback_strength===null&&i.revert_when_available===null?{}:{rotation:{defaultFallbackStrength:r(i.default_fallback_strength),revertWhenAvailable:i.revert_when_available===null?null:o(i.revert_when_available)===1}},...i===void 0||r(i.openai_device_id)===null?{}:{openaiDeviceId:r(i.openai_device_id)}},m={};for(const e of await t.prepare("SELECT owner_id, name, value_sealed, key_id FROM env_vars WHERE person = ? AND owner_kind = 'profile' ORDER BY owner_id, name").all(a)){const n=String(e.owner_id),l=String(e.name);(m[n]??={})[l]=c(e.value_sealed,String(e.key_id),d,O({person:a,kind:"profile",id:n},l))}const p={settings:{},state:{},profileProjects:{},defaults:{}};for(const e of await t.prepare("SELECT owner_key, raw_json FROM scope_settings WHERE person = ?").all(a))p.settings[String(e.owner_key)]=JSON.parse(String(e.raw_json));for(const e of await t.prepare("SELECT owner_key, last_self_check, active_canvas FROM scope_bookkeeping WHERE person = ?").all(a))p.state[String(e.owner_key)]={last_self_check:r(e.last_self_check),activeCanvas:r(e.active_canvas)};for(const e of await t.prepare("SELECT pp.profile_id, pp.path FROM profile_projects pp JOIN profiles p ON p.id = pp.profile_id WHERE p.person = ? ORDER BY pp.profile_id, pp.position").all(a))(p.profileProjects[String(e.profile_id)]??=[]).push(String(e.path));for(const e of await t.prepare("SELECT directory, profile_id FROM profile_defaults WHERE person = ?").all(a))p.defaults[String(e.directory)]=String(e.profile_id);const f={marketplaces:[],plugins:[]};for(const e of await t.prepare("SELECT * FROM scope_marketplaces WHERE person = ? ORDER BY owner_key, name").all(a))f.marketplaces.push({owner:String(e.owner_key),name:String(e.name),url:r(e.url),ref:r(e.ref),installed_at:r(e.installed_at),last_updated:r(e.last_updated)});for(const e of await t.prepare("SELECT * FROM scope_plugins WHERE person = ? ORDER BY owner_key, name").all(a))f.plugins.push({owner:String(e.owner_key),name:String(e.name),enabled:o(e.enabled)===1,source_marketplace:r(e.source_marketplace),version:r(e.version),source_path:r(e.source_path),last_updated:r(e.last_updated),etag:r(e.etag),checked_at:r(e.checked_at)});return{auth:{apiKeys:u},credentials:S,env:{profiles:m},settings:p,plugins:f}}_(M,"readStores");export{M as readStores};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CanvasDb } from '../../canvas/db.js';
|
|
2
|
+
import { type RewriteInput } from './rewrite.js';
|
|
3
|
+
/** A grant's or row's `sub` as the run sees it: the source's person is the run's, no other person is allowed. */
|
|
4
|
+
export declare function boundSub(sub: string, source: string, run: string, where: string): string;
|
|
5
|
+
/** Before anything is staged: the bundle's person has the shape of a person, and when it differs from the run's it is not already someone else in the target. */
|
|
6
|
+
export declare function checkSourcePerson(db: CanvasDb, target: string, source: string, run: string): Promise<void>;
|
|
7
|
+
/** After staging, before rewrite 1: every person the staged rows name is the bundle's or the run's. Drops the directory's own team facts
|
|
8
|
+
* (`crtr/group` relations). Returns rows dropped per table. */
|
|
9
|
+
export declare function checkStagedPersons(input: RewriteInput): Promise<Record<string, number>>;
|
|
10
|
+
/** After rewrite 2 and the drop: every reference from a staged row to a target row reaches a row the run's person owns or acts for (an app, a shipped
|
|
11
|
+
* document, the person's own object, an object with an `acts_for` fact for the person, or anything below those that is not directly under an app), and no profile or grant the
|
|
12
|
+
* rows name belongs to another person. */
|
|
13
|
+
export declare function checkStagedReferences(input: RewriteInput): Promise<void>;
|
|
14
|
+
/** After staging, before rewrite 1: every blob key a staged row names has the shape its row would produce and an owner (node) that is in this bundle.
|
|
15
|
+
* A key naming another person's blob is `person_mismatch`; a key of no known shape is `bundle_invalid`. Without this, the importer's own node would read, and
|
|
16
|
+
* compaction would delete, a blob of someone else's conversation. (Human requests are checked by `checkStagedRequestKeys`, after their dirs are rewritten to blob keys.) */
|
|
17
|
+
export declare function checkStagedBlobKeys(input: RewriteInput): Promise<void>;
|
|
18
|
+
/** After rewrite 4b: a human request's `request_dir`, `presentation_dir` and `file` that are blob keys (`requests/…`) are under the request's own `requests/<its id>`. */
|
|
19
|
+
export declare function checkStagedRequestKeys(input: RewriteInput): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
var g=Object.defineProperty;var u=(s,t)=>g(s,"name",{value:t,configurable:!0});import{parseBlobKey as L}from"../bundle/blob-keys.js";import{q as E}from"./catalog.js";import{objectIdColumns as R}from"./rewrite.js";import{ImportLaneError as m}from"./stage.js";const w=/^user:[A-Za-z0-9._-]+$/,S=u(s=>s.replace(/^user:/,""),"bare"),I=u((s,t)=>`${E(s)}.${E(t)}`,"sq"),$=u(async(s,t,...r)=>await s.prepareUncached(t).all(...r),"all");function _(s,t){return new m("bind","person_mismatch",s,t)}u(_,"mismatch");function D(s,t,r,a){if(s===t||s===r)return r;throw _(`${a} names ${s}, which is neither the bundle's person nor the importing person`,{where:a,value:s})}u(D,"boundSub");async function k(s,t,r,a){if(!w.test(r))throw _(`the bundle's person ${JSON.stringify(r)} is not a user:<id> principal`,{where:"source.json person",value:r});if(r===a)return;const i=u(e=>I(t,e),"T"),n=await s.prepareUncached(`SELECT 'identities' AS tbl FROM ${i("identities")} WHERE scheme IN ('user', 'local-user') AND external_id = ?
|
|
2
|
+
UNION ALL SELECT 'grants' FROM ${i("grants")} WHERE sub = ? UNION ALL SELECT 'profiles' FROM ${i("profiles")} WHERE person = ?
|
|
3
|
+
UNION ALL SELECT 'nodes' FROM ${i("nodes")} WHERE sub = ? LIMIT 1`).get(r,r,r,r);if(n!==void 0)throw _(`the bundle's person ${r} is already a person in the target (${n.tbl}), not the importing person ${a}`,{where:`${n.tbl}`,value:r})}u(k,"checkSourcePerson");const v=[["nodes","sub"],["crons","sub"],["grants","sub"],["profiles","person"],["charges","sub"],["limits","sub"],["audit_rows","sub"],["provider_approvals","sub"]],C=[["charges","payer"],["limits","payer"],["audit_rows","payer"],["audit_rows","grantee"],["charges","grantee"],["run_events","grantee"],["idempotency_keys","grantee"],["uploads","grantee"],["provider_approvals","grantee"],["profiles","grantee"],["crons","grantee"]];async function H(s){const{db:t,schema:r,sourcePerson:a,targetPerson:i}=s,n=u(p=>I(r,p),"S"),e=u((p,d)=>{throw _(`${p} names ${d.join(", ")}, who is neither the bundle's person ${a} nor the importing person ${i}`,{where:p,values:d})},"refuse");for(const[p,d]of v){const o=await $(t,`SELECT DISTINCT ${E(d)} AS v FROM ${n(p)} WHERE ${E(d)} IS NOT NULL AND ${E(d)} <> '' AND ${E(d)} NOT IN (?, ?) LIMIT 5`,a,i);o.length>0&&e(`${p}.${d}`,o.map(l=>l.v))}for(const[p,d]of C){const o=await $(t,`SELECT DISTINCT ${E(d)} AS v FROM ${n(p)} WHERE ${E(d)} LIKE 'user:%' AND ${E(d)} NOT IN (?, ?) LIMIT 5`,a,i);o.length>0&&e(`${p}.${d}`,o.map(l=>l.v))}const c=await $(t,`SELECT DISTINCT external_id AS v FROM ${n("identities")} WHERE scheme IN ('user', 'local-user') AND external_id NOT IN (?, ?) LIMIT 5`,a,i);c.length>0&&e("identities.external_id",c.map(p=>p.v));const f=await $(t,`SELECT DISTINCT adapter_json::jsonb #>> '{principal,sub}' AS v FROM ${n("daemon_actions")}
|
|
4
|
+
WHERE adapter_json::jsonb #>> '{principal,sub}' IS NOT NULL AND adapter_json::jsonb #>> '{principal,sub}' NOT IN (?, ?) LIMIT 5`,a,i);f.length>0&&e("daemon_actions principal.sub",f.map(p=>p.v));const h=await $(t,`SELECT DISTINCT subject_id AS v FROM ${n("relations")} WHERE relation = 'acts_for' AND subject_type = 'user' AND subject_id NOT IN (?, ?) LIMIT 5`,S(a),S(i));h.length>0&&e("relations acts_for subject",h.map(p=>p.v));const N=Number((await t.prepareUncached(`DELETE FROM ${n("relations")} WHERE resource_type = 'crtr/group'`).run()).changes);return N>0?{relations:N}:{}}u(H,"checkStagedPersons");async function W(s){const{db:t,schema:r,target:a,catalog:i,targetPerson:n}=s,e=u(o=>I(r,o),"S"),c=u(o=>I(a,o),"T"),f=S(n);await t.exec(`CREATE TABLE ${e("_refs")} (tbl text NOT NULL, col text NOT NULL, value text NOT NULL)`);const h=[];for(const[o,l]of R(i))if(!(!i.tables.has(o)||o==="relations"))for(const b of l)!(o==="nodes"&&b==="profile_id")&&!(o==="crons"&&b==="profile")&&h.push([o,b,""]);h.push(["relations","resource_id","WHERE resource_type = 'crtr/object'"]);for(const[o,l,b]of h)await t.prepareUncached(`INSERT INTO ${e("_refs")} SELECT ?, ?, ${E(l)}::text FROM ${e(o)} ${b===""?"WHERE":`${b} AND`} ${E(l)} IS NOT NULL
|
|
5
|
+
AND ${E(l)}::text NOT IN (SELECT id FROM ${e("objects")}) AND ${E(l)}::text IN (SELECT id FROM ${c("objects")})`).run(o,l);const N=await $(t,`WITH RECURSIVE chain(start, id) AS (
|
|
6
|
+
SELECT DISTINCT value, value FROM ${e("_refs")}
|
|
7
|
+
UNION SELECT c.start, o.owner FROM chain c JOIN ${c("objects")} o ON o.id = c.id JOIN ${c("objects")} up ON up.id = o.owner AND up.type <> 'app'),
|
|
8
|
+
ok(id) AS (
|
|
9
|
+
SELECT o.id FROM ${c("objects")} o WHERE o.type = 'app'
|
|
10
|
+
UNION SELECT d.object FROM ${c("documents")} d WHERE d.source_path IS NOT NULL
|
|
11
|
+
UNION SELECT i.object FROM ${c("identities")} i WHERE i.scheme IN ('user', 'local-user') AND i.external_id = ?
|
|
12
|
+
UNION SELECT r.resource_id FROM ${c("relations")} r WHERE r.resource_type = 'crtr/object' AND r.relation = 'acts_for' AND r.subject_type = 'user' AND r.subject_id = ?)
|
|
13
|
+
SELECT r.tbl, r.col, r.value FROM ${e("_refs")} r
|
|
14
|
+
WHERE NOT EXISTS (SELECT 1 FROM chain c JOIN ok ON ok.id = c.id WHERE c.start = r.value) LIMIT 10`,n,f);if(N.length>0)throw _(`the bundle refers to objects that are not ${n}'s: ${N.map(o=>`${o.tbl}.${o.col}=${o.value}`).join(", ")}`,{references:N});const p=[["nodes","profile_id"],["crons","profile"],["profiles","id"]];for(const[o,l]of p){const b=await $(t,`SELECT DISTINCT s.${E(l)} AS v FROM ${e(o)} s JOIN ${c("profiles")} p ON p.id = s.${E(l)} WHERE p.person <> ? LIMIT 5`,n);if(b.length>0)throw _(`${o}.${l} names profiles that belong to another person: ${b.map(T=>T.v).join(", ")}`,{where:`${o}.${l}`,values:b.map(T=>T.v)})}for(const[o,l]of[["nodes","grant_id"],["crons","grant_id"]]){const b=await $(t,`SELECT DISTINCT s.${E(l)} AS v FROM ${e(o)} s JOIN ${c("grants")} g ON g.id = s.${E(l)} WHERE g.sub <> ? LIMIT 5`,n);if(b.length>0)throw _(`${o}.${l} names grants that belong to another person: ${b.map(T=>T.v).join(", ")}`,{where:`${o}.${l}`,values:b.map(T=>T.v)})}const d=await $(t,`SELECT DISTINCT d.adapter_json::jsonb #>> '{principal,grant_id}' AS v FROM ${e("daemon_actions")} d
|
|
15
|
+
JOIN ${c("grants")} g ON g.id = d.adapter_json::jsonb #>> '{principal,grant_id}' WHERE g.sub <> ? LIMIT 5`,n);if(d.length>0)throw _(`a queued action names grants that belong to another person: ${d.map(o=>o.v).join(", ")}`,{where:"daemon_actions principal.grant_id",values:d.map(o=>o.v)})}u(W,"checkStagedReferences");const O=u((s,t)=>new m("bind","bundle_invalid",s,t),"bundleInvalid");async function U(s){const{db:t,schema:r}=s,a=u(e=>I(r,e),"S"),i=new Set((await $(t,`SELECT node_id AS v FROM ${a("nodes")}`)).map(e=>e.v)),n=u((e,c,f,h)=>{const N=L(c);if(N===null||N.kind!==f||h!==void 0&&c!==h)throw O(`${e} holds ${JSON.stringify(c)}, which is not a ${f} blob key${h===void 0?"":` of the shape ${h}`}`,{where:e,value:c});if(!i.has(N.owner))throw _(`${e} holds ${c}, which belongs to node ${N.owner}, who is not in the bundle`,{where:e,value:c})},"check");for(const e of await $(t,`SELECT conversation_id, seq::text AS seq, blob_key FROM ${a("conversation_parts")}`))if(n("conversation_parts.blob_key",e.blob_key,"conversation"),e.blob_key.split("/")[2]!==e.conversation_id)throw O(`conversation_parts.blob_key ${e.blob_key} is not under its conversation ${e.conversation_id}`,{where:"conversation_parts.blob_key",value:e.blob_key});for(const e of await $(t,`SELECT blob_key FROM ${a("upload_uses")} WHERE blob_key IS NOT NULL`))n("upload_uses.blob_key",e.blob_key,"attachment");for(const e of await $(t,`SELECT object, output_key FROM ${a("jobs")} WHERE output_key IS NOT NULL`)){const c=e.output_key.split("/")[1]??"";n("jobs.output_key",e.output_key,"job",`jobs/${c}/${e.object}/output`)}}u(U,"checkStagedBlobKeys");async function q(s){const{db:t,schema:r}=s;for(const a of await $(t,`SELECT request_id, request_dir, presentation_dir, file FROM ${I(r,"human_requests")}`))for(const i of["request_dir","presentation_dir","file"]){const n=a[i];if(n===null||!n.startsWith("requests/"))continue;const e=L(n.includes("/",9)?n:`${n}/x`);if(e===null||e.kind!=="request")throw O(`human_requests.${i} holds ${JSON.stringify(n)}, which is not a request blob key`,{where:`human_requests.${i}`,value:n});if(e.owner!==a.request_id)throw _(`human_requests.${i} of ${a.request_id} holds ${n}, which belongs to request ${e.owner}`,{where:`human_requests.${i}`,value:n})}}u(q,"checkStagedRequestKeys");export{D as boundSub,k as checkSourcePerson,U as checkStagedBlobKeys,H as checkStagedPersons,W as checkStagedReferences,q as checkStagedRequestKeys};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BlobStore } from '../../blobs/index.js';
|
|
2
|
+
import type { CanvasDb } from '../../canvas/db.js';
|
|
3
|
+
export interface MovedBlobs {
|
|
4
|
+
moved: number;
|
|
5
|
+
left: number;
|
|
6
|
+
}
|
|
7
|
+
/** `created` collects the keys this call wrote, so the caller can delete them if the run fails before its copy commits. */
|
|
8
|
+
export declare function moveRunBlobs(db: CanvasDb, schema: string, blobs: BlobStore, runId: string, created: string[]): Promise<MovedBlobs>;
|
|
9
|
+
/** Delete the run's uploaded copies (after the run is done). */
|
|
10
|
+
export declare function deleteRunBlobs(blobs: BlobStore, runId: string): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var S=Object.defineProperty;var r=(e,n)=>S(e,"name",{value:n,configurable:!0});import{parseBlobKey as q,runBlobPrefix as f}from"../bundle/blob-keys.js";import{q as m}from"./catalog.js";import{ImportLaneError as h}from"./stage.js";const E=8;async function k(e,n,o,g,y){const v={nodes:new Set((await e.prepareUncached(`SELECT node_id AS v FROM ${m(n)}.nodes`).all()).map(t=>t.v)),human_requests:new Set((await e.prepareUncached(`SELECT request_id AS v FROM ${m(n)}.human_requests`).all()).map(t=>t.v))},w=f(g),s=await o.list(w),i={moved:0,left:0};let p=0;const x=r(async()=>{for(let t=p++;t<s.length;t=p++){const l=s[t],a=l.slice(w.length),c=q(a);if(c===null||!v[c.ownerTable].has(c.owner)){i.left+=1;continue}const u=await o.get(l);if(u===null)throw new h("blobs","bundle_invalid",`${l} was listed and is gone`);const d=await o.get(a);if(d===null)await o.put(a,u),y.push(a);else if(!d.equals(u))throw new h("blobs","unique_conflict",`${a} already exists with other contents; the import does not replace it`,{key:a});i.moved+=1}},"worker");return await Promise.all(Array.from({length:Math.min(E,s.length)},x)),i}r(k,"moveRunBlobs");async function A(e,n){for(const o of await e.list(f(n)))await e.delete(o)}r(A,"deleteRunBlobs");export{A as deleteRunBlobs,k as moveRunBlobs};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CanvasDb } from '../../canvas/db.js';
|
|
2
|
+
export interface ForeignKey {
|
|
3
|
+
table: string;
|
|
4
|
+
columns: string[];
|
|
5
|
+
parent: string;
|
|
6
|
+
parentColumns: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface UniqueKey {
|
|
9
|
+
table: string;
|
|
10
|
+
name: string;
|
|
11
|
+
columns: string[];
|
|
12
|
+
predicate: string | null;
|
|
13
|
+
}
|
|
14
|
+
/** The columns that identify a row of `info`: its primary key, or every column in order for a table with none (`memory_reads`). */
|
|
15
|
+
export declare const rowKey: (info: TableInfo) => string[];
|
|
16
|
+
export interface TableInfo {
|
|
17
|
+
name: string;
|
|
18
|
+
columns: string[];
|
|
19
|
+
primaryKey: string[];
|
|
20
|
+
identity: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface Catalog {
|
|
23
|
+
schema: string;
|
|
24
|
+
tables: Map<string, TableInfo>;
|
|
25
|
+
foreignKeys: ForeignKey[];
|
|
26
|
+
uniqueKeys: UniqueKey[];
|
|
27
|
+
}
|
|
28
|
+
export declare const q: (name: string) => string;
|
|
29
|
+
/** Read the catalog of `schema` for `tables`. */
|
|
30
|
+
export declare function readCatalog(db: CanvasDb, schema: string, tables: readonly string[]): Promise<Catalog>;
|
|
31
|
+
/** Parents before children. Cycles (none in the schema today) are broken by the table list's order. */
|
|
32
|
+
export declare function topologicalOrder(catalog: Catalog, tables: readonly string[]): string[];
|