@north-light/cli 0.3.549 → 0.3.550
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/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/custom-objects.d.ts +1 -1
- package/dist/api/dto/docs.d.ts +12 -0
- package/dist/api/dto/objects.d.ts +17 -1
- package/dist/api/dto/objects.js +1 -1
- package/dist/api/dto/permissions.d.ts +76 -0
- package/dist/api/dto/permissions.js +1 -0
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.js +1 -1
- package/dist/api/routes.d.ts +4 -0
- package/dist/api/routes.js +1 -1
- package/dist/builtin-memory/crouter-concepts/documents.md +1 -1
- package/dist/builtin-memory/crouter-concepts/scopes-and-trust.md +2 -2
- package/dist/builtin-memory/crouter-sdk/release-notes.md +11 -0
- package/dist/builtin-memory/internal/INDEX.md +2 -1
- package/dist/builtin-memory/internal/agent-shaping.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/northlight-apps/guides/files-and-documents.md +3 -3
- package/dist/builtin-memory/northlight-apps/guides/many-users.md +1 -1
- package/dist/builtin-memory/northlight-apps/guides/test-your-app.md +1 -1
- package/dist/clients/attach/viewer.js +9 -9
- package/dist/commands/canvas/list.js +2 -2
- package/dist/commands/canvas/share.d.ts +2 -0
- package/dist/commands/canvas/share.js +2 -0
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/doc/edit.js +1 -1
- package/dist/commands/doc/shared.js +1 -1
- package/dist/commands/doc/write.js +1 -1
- package/dist/commands/sys/sync-skills.js +2 -2
- package/dist/core/canvas/canvas.js +20 -20
- package/dist/core/canvas/db.js +4 -4
- package/dist/core/canvas/export-sqlite.js +2 -2
- package/dist/core/canvas/import-sqlite.js +5 -5
- package/dist/core/canvas/outcome-actions.js +3 -3
- package/dist/core/canvas/person.js +11 -9
- package/dist/core/canvas/schema-pg.sql +49 -5
- package/dist/core/environments.js +1 -1
- package/dist/core/feed/feed.js +1 -1
- package/dist/core/grants/mirror.d.ts +9 -1
- package/dist/core/grants/mirror.js +2 -2
- package/dist/core/grants/sync.js +1 -1
- package/dist/core/graph/access.d.ts +8 -49
- package/dist/core/graph/access.js +4 -7
- package/dist/core/graph/custom.d.ts +5 -2
- package/dist/core/graph/custom.js +9 -9
- package/dist/core/graph/deletion.js +5 -4
- package/dist/core/graph/documents.d.ts +12 -3
- package/dist/core/graph/documents.js +13 -7
- package/dist/core/graph/edges.js +3 -3
- package/dist/core/graph/events.js +12 -8
- package/dist/core/graph/kinds/document.js +4 -4
- package/dist/core/graph/kinds/kind.js +1 -1
- package/dist/core/graph/kinds/node.js +5 -5
- package/dist/core/graph/names.d.ts +16 -1
- package/dist/core/graph/names.js +11 -3
- package/dist/core/graph/objects.d.ts +19 -2
- package/dist/core/graph/objects.js +16 -10
- package/dist/core/graph/package-docs.js +5 -5
- package/dist/core/graph/package-files.js +2 -2
- package/dist/core/graph/reader.js +3 -3
- package/dist/core/graph/repo-sync/exchange.js +2 -2
- package/dist/core/graph/rules.d.ts +7 -5
- package/dist/core/graph/rules.js +3 -3
- package/dist/core/graph/types.d.ts +12 -1
- package/dist/core/graph/types.js +1 -1
- package/dist/core/graph/watches.d.ts +1 -1
- package/dist/core/graph/watches.js +9 -8
- package/dist/core/peer/envelope.d.ts +1 -1
- package/dist/core/peer/envelope.js +1 -1
- package/dist/core/peer/stubs.d.ts +1 -1
- package/dist/core/peer/stubs.js +1 -1
- package/dist/core/permissions/boot.d.ts +17 -0
- package/dist/core/permissions/boot.js +18 -0
- package/dist/core/permissions/builtin.d.ts +8 -0
- package/dist/core/permissions/builtin.js +20 -0
- package/dist/core/permissions/ceiling.d.ts +57 -0
- package/dist/core/permissions/ceiling.js +22 -0
- package/dist/core/permissions/compile.d.ts +14 -0
- package/dist/core/permissions/compile.js +1 -0
- package/dist/core/permissions/errors.d.ts +7 -0
- package/dist/core/permissions/errors.js +1 -0
- package/dist/core/permissions/evaluator.d.ts +28 -0
- package/dist/core/permissions/evaluator.js +6 -0
- package/dist/core/permissions/facts.d.ts +56 -0
- package/dist/core/permissions/facts.js +2 -0
- package/dist/core/permissions/schema-parser.d.ts +29 -0
- package/dist/core/permissions/schema-parser.js +2 -0
- package/dist/core/permissions/schemas.d.ts +21 -0
- package/dist/core/permissions/schemas.js +2 -0
- package/dist/core/permissions/sql.d.ts +16 -0
- package/dist/core/permissions/sql.js +1 -0
- package/dist/core/runtime/broker/frame-client.d.ts +1 -1
- package/dist/core/runtime/broker/frame-client.js +1 -1
- package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
- package/dist/core/runtime/broker/frame-dispatch.js +1 -1
- package/dist/core/runtime/broker/frame-read.d.ts +4 -3
- package/dist/core/runtime/broker/frame-read.js +1 -1
- package/dist/core/runtime/broker-protocol.d.ts +1 -1
- package/dist/core/runtime/host.js +3 -3
- package/dist/core/runtime/placement.js +1 -1
- package/dist/core/runtime/roadmap.js +1 -1
- package/dist/core/substrate/delivery/corpus.d.ts +2 -2
- package/dist/core/substrate/delivery/corpus.js +3 -3
- package/dist/core/substrate/delivery/deliver.js +2 -2
- package/dist/core/substrate/delivery/render-event.js +6 -6
- package/dist/core/substrate/subject-fields.d.ts +2 -0
- package/dist/core/substrate/subject.js +1 -1
- package/dist/daemon/api/app-listener.js +12 -12
- package/dist/daemon/api/bridge.d.ts +1 -1
- package/dist/daemon/api/bridge.js +4 -4
- package/dist/daemon/api/handlers/attach.d.ts +4 -1
- package/dist/daemon/api/handlers/attach.js +1 -1
- package/dist/daemon/api/handlers/canvas.js +5 -5
- package/dist/daemon/api/handlers/check.d.ts +2 -0
- package/dist/daemon/api/handlers/check.js +1 -0
- package/dist/daemon/api/handlers/custom-objects.js +2 -2
- package/dist/daemon/api/handlers/docs.js +1 -1
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/memory.js +7 -6
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/objects.js +3 -3
- package/dist/daemon/api/handlers/relations.d.ts +2 -0
- package/dist/daemon/api/handlers/relations.js +1 -0
- package/dist/daemon/api/handlers/schemas.d.ts +12 -0
- package/dist/daemon/api/handlers/schemas.js +1 -0
- package/dist/daemon/api/handlers/shares.d.ts +2 -0
- package/dist/daemon/api/handlers/shares.js +1 -0
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/router.d.ts +10 -6
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/crtrd.js +12 -12
- package/dist/daemon/reconcilers/delivery-lane.js +2 -2
- package/dist/migrations/004-canvas-documents/fields.d.ts +1 -1
- package/dist/migrations/004-canvas-documents/fields.js +2 -2
- package/docs/apps/guides/files-and-documents.md +3 -3
- package/docs/apps/guides/many-users.md +1 -1
- package/docs/apps/guides/test-your-app.md +1 -1
- package/docs/cli/documents-and-preferences.md +1 -1
- package/docs/concepts/documents.md +1 -1
- package/docs/concepts/scopes-and-trust.md +2 -2
- package/docs/sdk/release-notes.md +11 -0
- package/package.json +2 -2
- package/packages/crouter-identity/dist/index.js +14 -1
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
- package/dist/core/graph/spaces/app.d.ts +0 -3
- package/dist/core/graph/spaces/app.js +0 -1
- package/dist/core/graph/spaces/index.d.ts +0 -18
- package/dist/core/graph/spaces/index.js +0 -1
- package/dist/core/graph/spaces/own-memory.d.ts +0 -18
- package/dist/core/graph/spaces/own-memory.js +0 -1
- package/dist/core/graph/spaces/person.d.ts +0 -5
- package/dist/core/graph/spaces/person.js +0 -3
- package/dist/core/graph/spaces/repo.d.ts +0 -14
- package/dist/core/graph/spaces/repo.js +0 -1
- package/dist/core/runs/confinement.d.ts +0 -25
- package/dist/core/runs/confinement.js +0 -2
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var V=Object.defineProperty;var f=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as k,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as ee,OBJECT_COLUMNS as te,pgColumns as ne,quote as c,TABLE_ORDER as C}from"./import-sqlite.js";const re=5e3,se=new Set(["environments"]),oe={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ae={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version",ee,F]);function ie(i){const a=J(A(Q(),"crtr-export-")),r=process.env.CRTR_HOME;process.env.CRTR_HOME=a,k(A(a,"runtime","nodes"),{recursive:!0});try{const s=new W(i);return s.exec("PRAGMA journal_mode = DELETE;"),s.exec("PRAGMA foreign_keys = ON;"),z(s),s.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),s}finally{r===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=r,P(a,{recursive:!0,force:!0})}}f(ie,"createEmptyDatabase");async function
|
|
1
|
+
var V=Object.defineProperty;var f=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as k,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as ee,OBJECT_COLUMNS as te,pgColumns as ne,quote as c,TABLE_ORDER as C}from"./import-sqlite.js";const re=5e3,se=new Set(["environments","relations","schemas"]),oe={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ae={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version",ee,F]);function ie(i){const a=J(A(Q(),"crtr-export-")),r=process.env.CRTR_HOME;process.env.CRTR_HOME=a,k(A(a,"runtime","nodes"),{recursive:!0});try{const s=new W(i);return s.exec("PRAGMA journal_mode = DELETE;"),s.exec("PRAGMA foreign_keys = ON;"),z(s),s.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),s}finally{r===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=r,P(a,{recursive:!0,force:!0})}}f(ie,"createEmptyDatabase");async function _e(i,a=()=>{}){const r=Z();if(Y(r))throw new Error(`${r} already exists; refusing to overwrite it`);k(A(r,".."),{recursive:!0,mode:448});const s=`${r}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,types:{getTypeParser:f(((l,o)=>l===20?d=>Number(d):j.types.getTypeParser(l,o)),"getTypeParser")}});await t.connect();let m;try{if((await t.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'").catch(()=>({rows:[]}))).rows.length===0)throw new Error("the Postgres database has no canvas schema; nothing to export");m=ie(s);const o=m;o.exec("PRAGMA foreign_keys = OFF;");const d=await ne(t),L=[...d.keys()].filter(e=>!se.has(e)&&!C.includes(e));if(L.length>0)throw new Error(`export: Postgres tables with no export rule: ${L.join(", ")}`);await t.query("BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ");const h={};for(const[e,O]of Object.entries(ae)){const _=new Map;for(const E of(await t.query(`SELECT ${O} AS id, seq FROM ${e}`)).rows)_.set(E.id,Number(E.seq));h[e]=_}const B=h.objects;let M=0;o.exec("BEGIN");for(const e of C){const O=Date.now(),_=new Set(o.prepare(`PRAGMA table_info(${c(e)})`).all().map(n=>n.name)),E=d.get(e);if(E===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=E.some(n=>n.name==="seq"),$=E.filter(n=>_.has(n.name)),K=new Set(te[e]??[]),R=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",v=$.map(n=>n.type==="jsonb"?`${c(n.name)}::text AS ${c(n.name)}`:c(n.name));e==="watches"&&v.push("watch_id AS _watch_id"),R&&v.push("seq AS _seq");const y=$.map(n=>c(n.name)),U=o.prepare(`INSERT INTO ${c(e)} (${R?["rowid",...y]:y}) VALUES (${(R?["?",...y]:y).map(()=>"?").join(", ")})`),q=G?"seq":oe[e],T=`export_${e}`;await t.query(`DECLARE ${T} NO SCROLL CURSOR FOR SELECT ${v.join(", ")} FROM ${c(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${q===void 0?"":` ORDER BY ${q}`}`);let b=0;for(;;){const n=await t.query(`FETCH ${re} FROM ${T}`);if(n.rows.length===0)break;for(const N of n.rows){const D=R?[Number(N._seq)]:[];for(const p of $){let w=N[p.name];e==="revision_parents"&&(p.name==="child"||p.name==="parent")?w=x(h.revisions,"revision",e,p.name,w):e==="watch_subscriptions"&&p.name==="watch"?w=x(h.watches,"watch",e,p.name,w):K.has(p.name)&&(w=x(B,"object",e,p.name,w)),D.push(w??null)}U.run(...D)}b+=n.rows.length}await t.query(`CLOSE ${T}`),M+=b,a(`export ${e}: ${b} rows in ${Date.now()-O} ms`)}o.exec("COMMIT"),await t.query("COMMIT");const g=o.prepare("PRAGMA foreign_key_check").all();if(g.length>0)throw new Error(`export: ${g.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(g[0])}`);await ce(t,o);const S=o.prepare("PRAGMA user_version").get().user_version;if(S!==I.length)throw new Error(`export: exported canvas.db is at user_version ${S}, expected ${I.length}`);return o.close(),m=void 0,await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[F,JSON.stringify({at:new Date().toISOString(),path:r})]),X(s,r),{path:r,rows:M,ms:Date.now()-u,userVersion:S}}catch(l){await t.query("ROLLBACK").catch(()=>{});try{m?.close()}catch{}for(const o of["","-wal","-shm","-journal"])P(`${s}${o}`,{force:!0});throw l}finally{await t.end().catch(()=>{})}}f(_e,"exportPostgresToSqlite");function x(i,a,r,s,u){if(u==null)return null;const t=i.get(u);if(t===void 0)throw new Error(`export: ${r}.${s} references ${a} ${String(u)}, which was not exported (`);return t}f(x,"mapId");async function ce(i,a){const r=[];for(const s of C){const u=Number((await i.query(`SELECT count(*) AS n FROM ${c(s)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${c(s)}`).get().n),m=u;s==="canvas_meta"&&(m=Number((await i.query("SELECT count(*) AS n FROM canvas_meta WHERE key <> ALL($1)",[[...H]])).rows[0].n),t=Number(a.prepare("SELECT count(*) AS n FROM canvas_meta").get().n)),m!==t&&r.push(`${s}: Postgres ${m} rows, SQLite ${t}`)}if(r.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
|
|
2
2
|
${r.join(`
|
|
3
|
-
`)}`)}f(ce,"verify");export{
|
|
3
|
+
`)}`)}f(ce,"verify");export{_e as exportPostgresToSqlite};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Q=Object.defineProperty;var m=(t,o)=>Q(t,"name",{value:o,configurable:!0});import{DatabaseSync as X}from"node:sqlite";import{createHash as z,randomBytes as Z}from"node:crypto";import{existsSync as O,mkdirSync as ee,renameSync as F}from"node:fs";import{dirname as te}from"node:path";import oe from"pg";import{withExclusiveDirectoryLockAsync as se}from"../exclusive-lock.js";import{DAEMON_STARTUP_WINDOW_MS as re}from"../../daemon/startup-policy.js";import{migrate as ne}from"./migrations.js";import{readLegacyPrivacy as ae}from"../graph/types.js";import{canvasDbPath as B,onDiskMigrationLockPath as ie}from"./paths.js";const q="imported_from_sqlite",U="exported_to_sqlite",M=1e4,L=["objects","documents","revisions","revision_parents","jobs","identities","custom_objects","context_sessions","peer_stubs","object_kinds","watches","watch_subscriptions","exposures","rules","edges","unresolved_links","nodes","focuses","canvas_meta","runtime_state","warm_pool","sessions","executions","worktrees","node_faults","node_tokens","node_outcome_deliveries","deleted_isolated_nodes","create_requests","waits","messages","message_sequence","message_causes","deliveries","structured_requests","daemon_actions","human_requests","human_action_deliveries","review_comments","review_comment_events","run_events","idempotency_keys","grants","memory_reads","app_users","uploads","upload_uses","shares","audit_rows","grantee_usage","provider_receipts","provider_approvals","usage","crons","cron_runs","peer_outbox","peer_locations","peer_ingested","peer_ingested_destinations"],ce=new Set(["sqlite_sequence","sqlite_stat1","sqlite_stat4","reviews"]),ue={objects:["owner"],documents:["object"],revisions:["document","author"],jobs:["object"],identities:["object"],custom_objects:["object"],context_sessions:["node"],peer_stubs:["object"],object_kinds:["owner"],watches:["watcher","object","link_app"],exposures:["node","object"],rules:["object","owner"],edges:["src","dst"],unresolved_links:["src"]},pe={objects:["schema_type"],nodes:["app"],revisions:["acting_app","acting_for"]},W=new Set(["daemon_owner","schema_version"]),de={canvas_meta:"key",message_sequence:"id"};function H(){return Z(16).toString("hex")}m(H,"newId");function f(t){return`"${t.replace(/"/g,'""')}"`}m(f,"quote");function me(t){switch(t){case"bigint":return"bigint[]";case"double precision":return"float8[]";case"bytea":return"bytea[]";case"text":case"jsonb":return"text[]";default:throw new Error(`import: no array type for Postgres column type ${t}`)}}m(me,"arrayType");async function fe(t){const o=await t.query(`SELECT c.relname AS tbl, a.attname AS col, format_type(a.atttypid, a.atttypmod) AS type, a.attidentity <> '' AS identity
|
|
2
2
|
FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid
|
|
3
3
|
WHERE c.relnamespace = current_schema()::regnamespace AND c.relkind = 'r' AND a.attnum > 0 AND NOT a.attisdropped
|
|
4
|
-
ORDER BY c.relname, a.attnum`),e=new Map;for(const
|
|
5
|
-
`);return c.digest("hex")},"hash"),
|
|
6
|
-
${
|
|
7
|
-
`)}`)}
|
|
4
|
+
ORDER BY c.relname, a.attnum`),e=new Map;for(const a of o.rows){const r=e.get(a.tbl)??[];r.push({name:a.col,type:a.type,identity:a.identity}),e.set(a.tbl,r)}return e}m(fe,"pgColumns");const we="\uFFFD";function Ee(t){if(!t.includes("\\u0000"))return{text:t,replaced:0};let o;try{o=JSON.parse(t)}catch{return{text:t,replaced:0}}let e=0;const a=m(i=>{const u=i.split("\0");return e+=u.length-1,u.join(we)},"clean"),r=m(i=>typeof i=="string"?a(i):Array.isArray(i)?i.map(r):i!==null&&typeof i=="object"?Object.fromEntries(Object.entries(i).map(([u,l])=>[a(u),r(l)])):i,"walk"),E=r(o);return e===0?{text:t,replaced:e}:{text:JSON.stringify(E),replaced:e}}m(Ee,"scrubNul");async function le(t,o,e,a,r,E,i){for(let u=0;u<a.length;u++)for(const l of e){const n=r[E.indexOf(l)][u];if(n!=null){await t.query("SAVEPOINT import_probe");try{await t.query("SELECT $1::jsonb",[n]),await t.query("RELEASE SAVEPOINT import_probe")}catch(c){return await t.query("ROLLBACK TO SAVEPOINT import_probe"),new Error(`import: ${o}.${l.name} at ${i(a[u])} is not a value jsonb accepts: ${c.message}`,{cause:c})}}}}m(le,"describeJsonbFailure");function j(t,o,e,a,r){if(r==null)return null;const E=t.get(Number(r));if(E===void 0)throw new Error(`import: ${e}.${a} references ${o} rowid ${String(r)}, which does not exist`);return E}m(j,"lookup");async function ye(t,o,e,a,r,E){const i=a.filter(s=>!(pe[e]??[]).includes(s.name)),u=t.prepare(`PRAGMA table_info(${f(e)})`).all(),l=new Set(u.map(s=>s.name)),c=!t.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get(e).sql.toUpperCase().includes("WITHOUT ROWID"),_=new Set(ue[e]??[]),h=[];for(const s of i)if(s.name==="seq"&&!l.has("seq")){if(!c)throw new Error(`import: ${e} needs seq from rowid but has none`);h.push("rowid AS seq")}else if(s.name==="watch_id"||s.name==="subscription_id")h.push("NULL AS "+f(s.name));else if(l.has(s.name))h.push(f(s.name));else throw new Error(`import: SQLite table ${e} has no column ${s.name}`);c&&h.push("rowid AS _rowid");const S=e==="canvas_meta"?` WHERE key NOT IN (${[...W].map(s=>`'${s}'`).join(",")})`:"",y=t.prepare(`SELECT ${h.join(", ")} FROM ${f(e)}${S}${c?" ORDER BY rowid":""}`),$=i.map(s=>f(s.name)),g=i.map((s,p)=>`$${p+1}::${me(s.type)}`),T=i.map((s,p)=>`c${p}`),A=i.map((s,p)=>s.type==="jsonb"?`c${p}::jsonb`:`c${p}`);let v=`INSERT INTO ${f(e)} (${$.join(", ")}) SELECT ${A.join(", ")} FROM unnest(${g.join(", ")}) AS u(${T.join(", ")})`;const N=de[e];if(N!==void 0){const s=i.filter(p=>p.name!==N).map(p=>`${f(p.name)} = EXCLUDED.${f(p.name)}`);v+=` ON CONFLICT (${f(N)}) DO UPDATE SET ${s.join(", ")}`}const I=e==="objects",D=i.filter(s=>s.type==="jsonb"),k=c?"_rowid":i[0].name,V=m(s=>`${c?"rowid":k} ${String(s[k])}`,"key");let x=0,b=[];const P=m(async()=>{if(b.length===0)return;const s=i.map(()=>new Array(b.length));if(b.forEach((p,R)=>{i.forEach((d,J)=>{let w=p[d.name];if(e==="objects"&&d.name==="privacy"?w=ae(w):d.name==="watch_id"||d.name==="subscription_id"?w=H():e==="revision_parents"&&(d.name==="child"||d.name==="parent")?w=j(r.revisions,"revision",e,d.name,w):e==="watch_subscriptions"&&d.name==="watch"?w=j(r.watches,"watch",e,d.name,w):_.has(d.name)&&(w=I?null:j(r.objects,"object",e,d.name,w)),d.type==="bytea"&&w instanceof Uint8Array&&!Buffer.isBuffer(w)&&(w=Buffer.from(w.buffer,w.byteOffset,w.byteLength)),d.type==="jsonb"&&typeof w=="string"){const C=Ee(w);C.replaced>0&&(w=C.text,E.set(`${e}.${d.name}`,(E.get(`${e}.${d.name}`)??0)+C.replaced))}s[J][R]=w??null}),e==="watches"&&r.watches.set(Number(p._rowid),s[i.findIndex(d=>d.name==="watch_id")][R])}),D.length===0)await o.query(v,s);else{await o.query("SAVEPOINT import_batch");try{await o.query(v,s)}catch(p){throw await o.query("ROLLBACK TO SAVEPOINT import_batch"),await le(o,e,D,b,s,i,V)??p}await o.query("RELEASE SAVEPOINT import_batch")}x+=b.length,b=[]},"flush");for(const s of y.iterate())e==="objects"&&r.objects.set(Number(s._rowid),s.id),e==="revisions"&&r.revisions.set(Number(s._rowid),s.rev_id),b.push(s),b.length>=M&&await P();if(await P(),I){const s=t.prepare("SELECT id, owner FROM objects WHERE owner IS NOT NULL").all();for(let p=0;p<s.length;p+=M){const R=s.slice(p,p+M);await o.query("UPDATE objects o SET owner = v.owner FROM unnest($1::text[], $2::text[]) AS v(id, owner) WHERE o.id = v.id",[R.map(d=>d.id),R.map(d=>j(r.objects,"object","objects","owner",d.owner))])}}return x}m(ye,"copyTable");async function _e(t,o){for(const[e,a]of o)for(const r of a)r.identity&&await t.query(`SELECT setval(pg_get_serial_sequence($1, $2), COALESCE((SELECT max(${f(r.name)}) FROM ${f(e)}), 1), (SELECT max(${f(r.name)}) IS NOT NULL FROM ${f(e)}))`,[f(e),r.name])}m(_e,"advanceIdentities");function G(t,o,e=""){return Number(t.prepare(`SELECT count(*) AS n FROM ${f(o)}${e}`).get().n)}m(G,"sqliteCount");async function he(t,o,e){const a=[];for(const n of L){if(n==="canvas_meta")continue;const c=G(t,n),_=Number((await o.query(`SELECT count(*) AS n FROM ${f(n)}`)).rows[0].n);c!==_&&a.push(`${n}: SQLite ${c} rows, Postgres ${_}`)}for(const n of t.prepare(`SELECT key, value FROM canvas_meta WHERE key NOT IN (${[...W].map(c=>`'${c}'`).join(",")})`).all())(await o.query("SELECT value FROM canvas_meta WHERE key = $1",[n.key])).rows[0]?.value!==n.value&&a.push(`canvas_meta ${n.key}: value differs`);const r=m(n=>{const c=z("sha256");for(const _ of n)c.update(_).update(`
|
|
5
|
+
`);return c.digest("hex")},"hash"),E=t.prepare("SELECT id FROM objects ORDER BY rowid").all().map(n=>n.id),i=(await o.query("SELECT id FROM objects ORDER BY seq")).rows.map(n=>n.id);r(E)!==r(i)&&a.push("objects: ids in insertion order differ");const u=t.prepare("SELECT o.id AS id, p.id AS owner FROM objects o JOIN objects p ON p.rowid = o.owner ORDER BY o.rowid").all(),l=(await o.query("SELECT id, owner FROM objects WHERE owner IS NOT NULL ORDER BY seq")).rows;r(u.map(n=>`${n.id}>${n.owner}`))!==r(l.map(n=>`${n.id}>${n.owner}`))&&a.push("objects: owner links differ");for(const[n,c]of[["run_events","body_json"],["messages","body_json"],["deliveries","last_failure_json"]]){const _=Number(t.prepare(`SELECT COALESCE(sum(length(CAST(${f(c)} AS TEXT))), 0) AS n FROM ${f(n)}`).get().n),h=e.get(n).find($=>$.name===c).type,S=h==="jsonb"?`${f(c)}::text`:f(c);if(h==="jsonb")continue;const y=Number((await o.query(`SELECT COALESCE(sum(length(${S})), 0)::bigint AS n FROM ${f(n)}`)).rows[0].n);_!==y&&a.push(`${n}.${c}: total length SQLite ${_}, Postgres ${y}`)}if(a.length>0)throw new Error(`SQLite \u2192 Postgres import verification failed (rolled back; canvas.db untouched):
|
|
6
|
+
${a.join(`
|
|
7
|
+
`)}`)}m(he,"verify");async function $e(t,o,e={}){const a=e.log??(()=>{}),r=Date.now(),E=await fe(o),i=t.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map(y=>y.name),u=i.filter(y=>!ce.has(y)&&!L.includes(y));if(u.length>0)throw new Error(`import: SQLite tables with no copy rule: ${u.join(", ")}`);const l=L.filter(y=>!E.has(y));if(l.length>0)throw new Error(`import: Postgres tables missing from the schema: ${l.join(", ")}`);const n=i.includes("reviews")?G(t,"reviews"):0;if(n>0)throw new Error(`import: the legacy reviews table still holds ${n} rows after the SQLite migration chain`);const c={objects:new Map,revisions:new Map,watches:new Map},_=[],h=new Map;let S=0;await o.query("BEGIN");try{await o.query("ALTER TABLE nodes DISABLE TRIGGER USER"),await o.query("ALTER TABLE deliveries DISABLE TRIGGER USER");for(const $ of L){const g=Date.now(),T=await ye(t,o,$,E.get($),c,h),A=Date.now()-g;_.push({table:$,rows:T,ms:A}),a(`import ${$}: ${T} rows in ${A} ms`)}await o.query("ALTER TABLE nodes ENABLE TRIGGER USER"),await o.query("ALTER TABLE deliveries ENABLE TRIGGER USER"),await _e(o,E);const y=Date.now();await he(t,o,E),S=Date.now()-y,a(`import verify: ${S} ms`),await o.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2)",[q,JSON.stringify({at:new Date().toISOString(),rows:_.reduce(($,g)=>$+g.rows,0)})]),await o.query("COMMIT")}catch(y){throw await o.query("ROLLBACK").catch(()=>{}),y}return h.size>0&&a(`import: replaced NUL (\\u0000) with U+FFFD in jsonb values: ${[...h].map(([y,$])=>`${y} ${$}`).join(", ")}`),{tables:_,verifyMs:S,totalMs:Date.now()-r,nulReplaced:Object.fromEntries(h)}}m($e,"copySqliteToPostgres");const Y=["","-wal","-shm"];function K(t=B()){if(O(`${t}.imported`)){const o=`${t}.imported.${Date.now()}`;for(const e of Y)O(`${t}.imported${e}`)&&F(`${t}.imported${e}`,`${o}${e}`)}for(const o of Y)O(`${t}${o}`)&&F(`${t}${o}`,`${t}.imported${o}`)}m(K,"renameImportedDatabase");async function Ce(t,o=()=>{}){const e=B();if(!O(e))return null;const a=ie();return ee(te(a),{recursive:!0,mode:448}),se(a,async()=>{if(!O(e))return null;const r=new oe.Client({connectionString:t,application_name:`crtr-import-${process.pid}`});await r.connect();try{const E=await r.query("SELECT key FROM canvas_meta WHERE key = ANY($1)",[[q,U]]),i=new Set(E.rows.map(n=>n.key));if(i.has(q)){if(i.has(U))throw new Error(`${e} was written by \`crtr sys db export-sqlite\` and the Postgres database predates it; importing would drop the exported writes. Stop the daemon and delete the Postgres cluster (runtime/pg/data) to import ${e} afresh, or move ${e} aside to keep using Postgres.`);return K(e),o(`import: finished the rename of ${e} (already imported)`),null}const u=new X(e);let l;try{u.exec("PRAGMA busy_timeout = 5000;"),u.exec("PRAGMA foreign_keys = ON;"),ne(u),l=await $e(u,r,{log:o})}finally{u.close()}return K(e),o(`import: ${l.tables.reduce((n,c)=>n+c.rows,0)} rows in ${l.totalMs} ms; canvas.db renamed to canvas.db.imported`),l}finally{await r.end().catch(()=>{})}},{timeoutMs:re,timeoutError:m(()=>new Error(`timed out waiting for the canvas import lock: ${a}`),"timeoutError")})}m(Ce,"importSqliteCanvasIfPresent");export{U as EXPORT_MARK_KEY,q as IMPORT_MARK_KEY,ue as OBJECT_COLUMNS,L as TABLE_ORDER,$e as copySqliteToPostgres,Ce as importSqliteCanvasIfPresent,fe as pgColumns,f as quote,K as renameImportedDatabase};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var
|
|
1
|
+
var u=Object.defineProperty;var n=(e,t)=>u(e,"name",{value:t,configurable:!0});import{writeActsFor as d}from"../permissions/facts.js";import{createObject as m,nodeObject as p}from"../graph/objects.js";import{recordWatch as E}from"../graph/watches.js";import{argvReceiver as w}from"../graph/receivers.js";import{finalizeNodeMail as v,insertMessage as y}from"../feed/messages.js";import{deliveryFrom as f}from"./deliveries.js";async function s(e,t){const a=await e.prepare(`SELECT a.*, c.receiver_json, c.payload_json FROM objects a
|
|
2
2
|
JOIN objects n ON n.id = ? AND n.type = 'node'
|
|
3
3
|
JOIN custom_objects c ON c.object = a.id
|
|
4
4
|
WHERE a.type = 'action' AND a.name = ? AND a.owner = n.owner AND a.deleted_at IS NULL
|
|
5
|
-
ORDER BY a.seq DESC LIMIT 1`).get(
|
|
6
|
-
ON CONFLICT(object) DO UPDATE SET receiver_json = excluded.receiver_json, payload_json = excluded.payload_json`).run(
|
|
5
|
+
ORDER BY a.seq DESC LIMIT 1`).get(t,`outcome/${t}`);if(!a)return null;const r=JSON.parse(a.receiver_json),o=await e.prepare("SELECT * FROM deliveries WHERE destination_id = ? ORDER BY created_at DESC, seq DESC LIMIT 1").get(a.id);return{object:a,actionName:r.name,argv:r.argv,cwd:r.cwd,configOrigin:r.config_origin,payload:a.payload_json===null?null:JSON.parse(a.payload_json),delivery:o?f(o):null,updatedAt:r.updated_at??Date.parse(a.created_at)}}n(s,"readOutcomeAction");async function S(e,t,a){const r=await p(e,t),o=await s(e,t);if(o?.delivery)throw new Error(`outcome action ${t} already has a delivery`);const i=o?.object??await m(e,{type:"action",kind:null,owner:r.owner,privacy:"public",name:`outcome/${t}`,preview:a.actionName}),c=(await e.prepare("SELECT sub FROM nodes WHERE node_id = ?").get(t))?.sub;!o&&c&&await e.prepare("SELECT 1 FROM objects WHERE id = ? AND type = 'app'").get(r.owner)!==void 0&&await d(e,i.id,c);const l=JSON.stringify({argv:a.argv,cwd:a.cwd,config_origin:a.configOrigin,name:a.actionName,updated_at:o?Math.max(Date.now(),o.updatedAt+1):Date.parse(i.created_at)});await e.prepare(`INSERT INTO custom_objects (object, receiver_json, payload_json) VALUES (?, ?, ?)
|
|
6
|
+
ON CONFLICT(object) DO UPDATE SET receiver_json = excluded.receiver_json, payload_json = excluded.payload_json`).run(i.id,l,JSON.stringify(a.payload??null)),o?await e.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(a.actionName,i.id):(await e.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(i.id),await E(e,i,r,"spawn",{patterns:["outcome"],config:[{threshold:1,delivery:"after-turn"}]}))}n(S,"registerOutcomeAction");async function T(e,t){const a=await s(e,t);return a?a.delivery?!1:(await e.prepare("DELETE FROM objects WHERE id = ?").run(a.object.id),!0):!0}n(T,"unregisterOutcomeAction");async function A(e,t,a){const r=await s(e,t);if(!r||r.delivery)throw new Error(`outcome action ${t} cannot be delivered again`);const o=await p(e,t),i=v({senderNodeId:t,senderName:o.name,kind:"completed",label:`outcome node ${o.name}`,bodyJson:a,urgency:"normal"},null),c=await y(e,{...i,senderNodeId:t,senderName:o.name,kind:"completed",label:`outcome node ${o.name}`,disposition:null,event:{event:"outcome",objectId:t,level:1,actorId:t}});await w.enqueue(e,{messageId:c,event:"outcome",level:1,senderNodeId:t,elevatesTier:!1},[{id:r.object.id,reception:2,delivery:"after-turn"}])}n(A,"enqueueLateOutcomeAction");export{A as enqueueLateOutcomeAction,s as readOutcomeAction,S as registerOutcomeAction,T as unregisterOutcomeAction};
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
var
|
|
1
|
+
var v=Object.defineProperty;var o=(e,n)=>v(e,"name",{value:n,configurable:!0});import{randomBytes as U}from"node:crypto";import{CrtrError as T}from"../errors.js";import{readConfig as D}from"../config.js";import{joinedOwner as f}from"../directory/join.js";import{installLocalTerminalGrant as P}from"../grants/local.js";import{dropLocalGrantMirror as k}from"../grants/mirror.js";import{ownerForGrantee as H,ensureOwner as W}from"../graph/objects.js";import{canvasDatabaseUrl as C,CanvasConstraintError as x,withCanvasWrite as S}from"./db.js";import{LEGACY_LOCAL_PERSON as p,LEGACY_LOCAL_TERMINAL_GRANT as b}from"./legacy-person.js";const E="person_id",h="local_person_retired",M=/^user:[A-Za-z0-9._-]+$/,O="abcdefghijklmnopqrstuvwxyz0123456789";class m extends Error{static{o(this,"PersonUnresolvedError")}code="person_unresolved";constructor(){super("the install's person is not resolved yet"),this.name="PersonUnresolvedError"}}let d;const j=new Map;function $(e){d=e}o($,"setPersonId");function ae(){if(d===void 0)throw new m;return d}o(ae,"personId");async function se(){if(d!==void 0)return d;const e=await C(),n=j.get(e);if(n!==void 0)return n;const t=await G();if(t===null)throw new m;return j.set(e,t),t}o(se,"personIdAsync");function F(){const e=U(12);let n="user:";for(let t=0;t<12;t++)n+=O[e[t]%O.length];return n}o(F,"mintPersonId");async function N(e,n){return(await e.prepare("SELECT value FROM canvas_meta WHERE key = ?").get(n))?.value??null}o(N,"readMeta");async function l(e,n,t){await e.prepare("INSERT INTO canvas_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(n,t)}o(l,"writeMeta");async function q(){return await S(async e=>{const n=await N(e,E);if(n!==null)return{id:n,source:"stored"};const t=f();if(t!==null)return await l(e,E,t),{id:t,source:"joined"};if(D("user").runtime?.issuer){const a=await e.prepare("SELECT DISTINCT sub FROM grants").all();return a.length!==1?null:(await l(e,E,a[0].sub),{id:a[0].sub,source:"grants"})}const r=F();return await l(e,E,r),{id:r,source:"minted"}})}o(q,"resolvePerson");async function G(){return(await q())?.id??null}o(G,"resolvePersonId");const s=o(e=>Number(e.changes),"changes"),J=[["nodes","sub"],["node_tokens","sub"],["grants","sub"],["crons","sub"],["audit_rows","sub"],["provider_approvals","sub"],["audit_rows","grantee"],["run_events","grantee"],["node_tokens","grantee"],["uploads","grantee"],["provider_receipts","grantee"],["provider_approvals","grantee"]];function I(e,n){if(!M.test(n))throw new T("invalid_person",`${e} must be a user:<id> principal, got ${JSON.stringify(n)}`)}o(I,"assertPerson");async function L(e,n,t){if(I("from",n),I("to",t),n===t)throw new T("invalid_person","cannot substitute a person with itself");const r={};try{r.identities=s(await e.prepare("UPDATE identities SET scheme = 'user', external_id = ? WHERE scheme IN ('local-user', 'user') AND external_id = ?").run(t,n))}catch(i){throw i instanceof x?new T("person_conflict",`another person object already holds ${t}; merging two person objects is not supported (${i.message})`,void 0,{from:n,to:t}):i}r["daemon_actions.principal_sub"]=s(await e.prepare(`UPDATE daemon_actions SET adapter_json = jsonb_set(adapter_json::jsonb, '{principal,sub}', to_jsonb(?::text))::text
|
|
2
2
|
WHERE state IN ('pending', 'handed_off') AND adapter_json::jsonb ->> 'adapter' = 'cron_spawn'
|
|
3
|
-
AND adapter_json::jsonb #>> '{principal,sub}' = ?`).run(t,
|
|
3
|
+
AND adapter_json::jsonb #>> '{principal,sub}' = ?`).run(t,n));for(const[i,c]of J)r[`${i}.${c}`]=s(await e.prepareUncached(`UPDATE ${i} SET ${c} = ? WHERE ${c} = ?`).run(t,n));r["idempotency_keys.grantee"]=s(await e.prepare("DELETE FROM idempotency_keys WHERE grantee = ? AND key IN (SELECT key FROM idempotency_keys WHERE grantee = ?)").run(n,t))+s(await e.prepare("UPDATE idempotency_keys SET grantee = ? WHERE grantee = ?").run(t,n));const a=n.replace(/^user:/,""),u=t.replace(/^user:/,"");return r["relations.subject_id"]=s(await e.prepare(`DELETE FROM relations r WHERE r.subject_type = 'user' AND r.subject_id = ?
|
|
4
|
+
AND EXISTS (SELECT 1 FROM relations n WHERE n.subject_type = 'user' AND n.subject_id = ? AND n.resource_type = r.resource_type
|
|
5
|
+
AND n.resource_id = r.resource_id AND n.relation = r.relation AND n.subject_relation = r.subject_relation)`).run(a,u))+s(await e.prepare("UPDATE relations SET subject_id = ? WHERE subject_type = 'user' AND subject_id = ?").run(u,a)),r["relations.written_by"]=s(await e.prepare("UPDATE relations SET written_by = ? WHERE written_by = ?").run(t,n)),r["revisions.acting_for"]=s(await e.prepare("UPDATE revisions SET acting_for = ? WHERE acting_for = ?").run(t,n)),await e.prepare(`INSERT INTO grantee_usage (grantee, month, usd_equivalent)
|
|
4
6
|
SELECT ?::text, month, usd_equivalent FROM grantee_usage WHERE grantee = ?
|
|
5
|
-
ON CONFLICT(grantee, month) DO UPDATE SET usd_equivalent = grantee_usage.usd_equivalent + excluded.usd_equivalent`).run(t,
|
|
7
|
+
ON CONFLICT(grantee, month) DO UPDATE SET usd_equivalent = grantee_usage.usd_equivalent + excluded.usd_equivalent`).run(t,n),r["grantee_usage.grantee"]=s(await e.prepare("DELETE FROM grantee_usage WHERE grantee = ?").run(n)),await e.prepare(`INSERT INTO usage (grantee, month, payer, source, served_model, input_tokens, output_tokens, cached_tokens, usd_equivalent)
|
|
6
8
|
SELECT ?::text, month, payer, source, served_model, input_tokens, output_tokens, cached_tokens, usd_equivalent FROM usage WHERE grantee = ?
|
|
7
9
|
ON CONFLICT(grantee, month, payer, source, served_model) DO UPDATE SET
|
|
8
10
|
input_tokens = usage.input_tokens + excluded.input_tokens,
|
|
9
11
|
output_tokens = usage.output_tokens + excluded.output_tokens,
|
|
10
12
|
cached_tokens = usage.cached_tokens + excluded.cached_tokens,
|
|
11
13
|
usd_equivalent = CASE WHEN usage.usd_equivalent IS NULL AND excluded.usd_equivalent IS NULL THEN NULL
|
|
12
|
-
ELSE COALESCE(usage.usd_equivalent, 0) + COALESCE(excluded.usd_equivalent, 0) END`).run(t,
|
|
13
|
-
WHERE o.type = 'node' AND i.scheme = 'local-user' AND i.external_id = '${
|
|
14
|
+
ELSE COALESCE(usage.usd_equivalent, 0) + COALESCE(excluded.usd_equivalent, 0) END`).run(t,n),r["usage.grantee"]=s(await e.prepare("DELETE FROM usage WHERE grantee = ?").run(n)),r}o(L,"substitutePerson");async function Y(e){const n=`SELECT o.id FROM objects o JOIN identities i ON i.object = o.owner JOIN nodes n ON n.node_id = o.id
|
|
15
|
+
WHERE o.type = 'node' AND i.scheme = 'local-user' AND i.external_id = '${p}'`;return(await e.prepareUncached(`${n} LIMIT 1`).get()!==void 0?s(await e.prepareUncached(`UPDATE objects SET owner = ? WHERE id IN (${n})`).run((await H(e,"app:terminal")).id)):0)+s(await e.prepare("UPDATE node_tokens SET grantee = 'app:terminal' WHERE grantee = ?").run(p))+s(await e.prepare("UPDATE run_events SET grantee = 'app:terminal' WHERE grantee = ?").run(p))+s(await e.prepare("DELETE FROM idempotency_keys WHERE grantee = ? AND key IN (SELECT key FROM idempotency_keys WHERE grantee = 'app:terminal')").run(p))+s(await e.prepare("UPDATE idempotency_keys SET grantee = 'app:terminal' WHERE grantee = ?").run(p))}o(Y,"rewriteLegacyGrantees");async function z(e,n,t){const r=[b,`terminal:${n}`].filter(c=>c!==t),a=r.map(()=>"?").join(", "),u={};u["nodes.grant_id"]=s(await e.prepareUncached(`UPDATE nodes SET grant_id = ? WHERE grant_id IN (${a})`).run(t,...r)),u["node_tokens.grant"]=s(await e.prepareUncached(`UPDATE node_tokens SET "grant" = ? WHERE "grant" IN (${a})`).run(t,...r)),u["crons.grant_id"]=s(await e.prepareUncached(`UPDATE crons SET grant_id = ? WHERE grant_id IN (${a})`).run(t,...r));const i=t===null?"'null'::jsonb":"to_jsonb(?::text)";return u["daemon_actions.principal_grant_id"]=s(await e.prepareUncached(`UPDATE daemon_actions SET adapter_json = jsonb_set(adapter_json::jsonb, '{principal,grant_id}', ${i})::text
|
|
14
16
|
WHERE state IN ('pending', 'handed_off') AND adapter_json::jsonb ->> 'adapter' = 'cron_spawn'
|
|
15
|
-
AND adapter_json::jsonb #>> '{principal,grant_id}' IN (${
|
|
16
|
-
WHERE o.type = 'node' AND o.reception = 2 AND s.envelope_json ->> 'app' IS NULL AND i.scheme = 'app' AND i.external_id = 'app:terminal'`;if(await
|
|
17
|
-
SELECT
|
|
18
|
-
WHERE
|
|
17
|
+
AND adapter_json::jsonb #>> '{principal,grant_id}' IN (${a})`).run(...t===null?[]:[t],...r)),u}o(z,"repointGrantReferences");async function oe(){const e=await S(async n=>{const t={},r=o((w,g="")=>{for(const[y,R]of Object.entries(w))R!==0&&(t[g+y]=(t[g+y]??0)+R)},"add");let a=await N(n,E);if(a===null)throw new m;let u=null;const i=f();i!==null&&i!==a&&(r(await L(n,a,i),"join."),await l(n,E,i),u=a,a=i);const c=D("user").runtime?.issuer,_=await N(n,"install_id");if(_===null)throw new Error("retireLocalPerson: the install id is not stored");const A=await N(n,h)!==a;if(A){r({legacy_grantees:await Y(n)}),r(await L(n,p,a));const w=`SELECT o.id FROM objects o JOIN peer_stubs s ON s.object = o.id JOIN identities i ON i.object = o.owner
|
|
18
|
+
WHERE o.type = 'node' AND o.reception = 2 AND s.envelope_json ->> 'app' IS NULL AND i.scheme = 'app' AND i.external_id = 'app:terminal'`;if(await n.prepareUncached(`${w} LIMIT 1`).get()!==void 0){const g=await W(n,"person","user",a,"user");r({repaired_stubs:s(await n.prepareUncached(`UPDATE objects SET owner = ? WHERE id IN (${w})`).run(g.id))})}r({"nodes.sub_null_terminal":s(await n.prepare(`UPDATE nodes SET sub = ? WHERE sub IS NULL AND node_id IN (
|
|
19
|
+
SELECT n.node_id FROM nodes n JOIN identities i ON i.object = n.app
|
|
20
|
+
WHERE i.scheme = 'app' AND i.external_id = 'app:terminal')`).run(a))}),r(await z(n,_,c?null:`terminal:${_}`)),c&&r({local_grant_mirror:await k(n)}),await l(n,h,a)}return c||await P(_,a),{person:a,joinedFrom:u,retired:A,columns:t}});return $(e.person),e}o(oe,"retireLocalPerson");export{p as LEGACY_LOCAL_PERSON,m as PersonUnresolvedError,ae as personId,se as personIdAsync,q as resolvePerson,G as resolvePersonId,oe as retireLocalPerson,$ as setPersonId,L as substitutePerson};
|
|
@@ -15,7 +15,7 @@ CREATE TABLE objects (
|
|
|
15
15
|
type text NOT NULL,
|
|
16
16
|
kind text,
|
|
17
17
|
owner text REFERENCES objects(id),
|
|
18
|
-
privacy text NOT NULL DEFAULT 'public' CHECK (privacy IN ('public','
|
|
18
|
+
privacy text NOT NULL DEFAULT 'public' CHECK (privacy IN ('public','private')),
|
|
19
19
|
name text NOT NULL,
|
|
20
20
|
preview text NOT NULL DEFAULT '',
|
|
21
21
|
created_at text NOT NULL,
|
|
@@ -939,10 +939,53 @@ CREATE TABLE peer_ingested_destinations (
|
|
|
939
939
|
ingested_at bigint NOT NULL
|
|
940
940
|
);
|
|
941
941
|
|
|
942
|
-
-- ----------------------------------------------------------------
|
|
942
|
+
-- ---------------------------------------------------------------- permissions (cycle 3)
|
|
943
|
+
|
|
944
|
+
-- One permission model: relation facts, app schemas, the columns the evaluator and owner tree need, the narrowed privacy
|
|
945
|
+
-- levels, `relation_source` (facts plus each live object's, and each reaped node's, owner presented as its `parent`) and `node_rows` with `grantee` read
|
|
946
|
+
-- from `nodes.app`. Added after schema version 1 shipped: `ensureSchema` runs this block alone against an older database
|
|
947
|
+
-- (idempotent; the version stays 1). The whole block is one multi-statement query, so it applies entirely or not at all.
|
|
948
|
+
-- permissions:begin
|
|
949
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
950
|
+
resource_type text NOT NULL,
|
|
951
|
+
resource_id text NOT NULL,
|
|
952
|
+
relation text NOT NULL,
|
|
953
|
+
subject_type text NOT NULL,
|
|
954
|
+
subject_id text NOT NULL,
|
|
955
|
+
subject_relation text NOT NULL DEFAULT '',
|
|
956
|
+
written_by text NOT NULL,
|
|
957
|
+
created_at text NOT NULL,
|
|
958
|
+
PRIMARY KEY (resource_type, resource_id, relation, subject_type, subject_id, subject_relation)
|
|
959
|
+
);
|
|
960
|
+
CREATE INDEX IF NOT EXISTS relations_subject ON relations(subject_type, subject_id, relation);
|
|
961
|
+
CREATE INDEX IF NOT EXISTS relations_resource ON relations(resource_id);
|
|
962
|
+
|
|
963
|
+
CREATE TABLE IF NOT EXISTS schemas (
|
|
964
|
+
namespace text PRIMARY KEY,
|
|
965
|
+
app text NOT NULL,
|
|
966
|
+
version bigint NOT NULL,
|
|
967
|
+
source text NOT NULL,
|
|
968
|
+
compiled_json text NOT NULL,
|
|
969
|
+
registered_at text NOT NULL
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
ALTER TABLE nodes ADD COLUMN IF NOT EXISTS app text;
|
|
973
|
+
ALTER TABLE objects ADD COLUMN IF NOT EXISTS schema_type text;
|
|
974
|
+
ALTER TABLE revisions ADD COLUMN IF NOT EXISTS acting_app text;
|
|
975
|
+
ALTER TABLE revisions ADD COLUMN IF NOT EXISTS acting_for text;
|
|
976
|
+
CREATE INDEX IF NOT EXISTS objects_owner_live ON objects(owner) WHERE deleted_at IS NULL;
|
|
977
|
+
|
|
978
|
+
UPDATE objects SET privacy = 'private' WHERE privacy = 'friends';
|
|
979
|
+
ALTER TABLE objects DROP CONSTRAINT IF EXISTS objects_privacy_check,
|
|
980
|
+
ADD CONSTRAINT objects_privacy_check CHECK (privacy IN ('public','private'));
|
|
981
|
+
|
|
982
|
+
CREATE OR REPLACE VIEW relation_source AS
|
|
983
|
+
SELECT resource_type, resource_id, relation, subject_type, subject_id, subject_relation, written_by FROM relations
|
|
984
|
+
UNION ALL
|
|
985
|
+
SELECT 'crtr/object', id, 'parent', 'crtr/object', owner, '', 'owner' FROM objects
|
|
986
|
+
WHERE owner IS NOT NULL AND (deleted_at IS NULL OR type = 'node');
|
|
943
987
|
|
|
944
|
-
|
|
945
|
-
-- recreates this view.
|
|
988
|
+
DROP VIEW IF EXISTS node_rows;
|
|
946
989
|
CREATE VIEW node_rows AS
|
|
947
990
|
SELECT n.*,
|
|
948
991
|
o.name,
|
|
@@ -970,10 +1013,11 @@ SELECT n.*,
|
|
|
970
1013
|
f.provider_error_json AS fault_provider_error_json
|
|
971
1014
|
FROM nodes n
|
|
972
1015
|
JOIN objects o ON o.id = n.node_id
|
|
973
|
-
LEFT JOIN identities oi ON oi.object = o.owner
|
|
1016
|
+
LEFT JOIN identities oi ON oi.object = COALESCE(n.app, o.owner)
|
|
974
1017
|
LEFT JOIN executions e ON e.node_id = n.node_id AND e.state != 'exited'
|
|
975
1018
|
LEFT JOIN sessions s ON s.node_id = n.node_id AND s.unbound_at IS NULL
|
|
976
1019
|
LEFT JOIN node_faults f ON f.node_id = n.node_id AND f.active = 1;
|
|
1020
|
+
-- permissions:end
|
|
977
1021
|
|
|
978
1022
|
-- ---------------------------------------------------------------- triggers
|
|
979
1023
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var s=Object.defineProperty;var i=(e,r)=>s(e,"name",{value:r,configurable:!0});import{openDb as p,withCanvasWrite as
|
|
1
|
+
var s=Object.defineProperty;var i=(e,r)=>s(e,"name",{value:r,configurable:!0});import{openDb as p,withCanvasWrite as w}from"./canvas/db.js";import{createObject as u,ownerForGrantee as E}from"./graph/objects.js";import{writeActsFor as h}from"./permissions/facts.js";import{personIdAsync as v}from"./canvas/person.js";let t=null;async function S(e){const r=new Date().toISOString(),o=await w(async n=>{const c=await n.prepare("SELECT object FROM environments WHERE machine_id = ?").get(e);if(c!==void 0)return await n.prepare("UPDATE environments SET last_seen_at = ? WHERE machine_id = ?").run(r,e),{environmentId:c.object,created:!1};const m=await E(n,"app:terminal"),a=await u(n,{type:"environment",kind:"machine",owner:m.id,privacy:"public",name:e,preview:"machine"});return await h(n,a.id,await v()),await n.prepare("INSERT INTO environments (object, kind, machine_id, last_seen_at) VALUES (?, 'machine', ?, ?)").run(a.id,e,r),{environmentId:a.id,created:!0}});return t={environmentId:o.environmentId,machineId:e},o}i(S,"registerMachineEnvironment");async function T(e){t!==null&&await(await p()).prepare("UPDATE environments SET last_seen_at = ? WHERE machine_id = ?").run(e,t.machineId)}i(T,"touchMachineEnvironment");function g(){if(t===null)throw new Error("the machine environment is not registered yet");return t.environmentId}i(g,"machineEnvironmentId");export{g as machineEnvironmentId,S as registerMachineEnvironment,T as touchMachineEnvironment};
|
package/dist/core/feed/feed.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var O=Object.defineProperty;var i=(e,t)=>O(e,"name",{value:t,configurable:!0});import{cancelCronsOnWake as q,getNode as E}from"../canvas/index.js";import{runStructuredRequestEffects as g,settleStructuredRequestIn as R}from"../canvas/structured-requests.js";import{afterCommit as x,openDb as F,withCanvasWrite as k}from"../canvas/db.js";import{brokerRelayGeneration as M,recordBrokerRelease as W}from"../runtime/lifecycle.js";import{randomUUID as z}from"node:crypto";import{composeNodeOutcomeDocument as A}from"../runtime/outcome-document.js";import{closeNode as L}from"../runtime/close.js";import{put as H}from"../graph/bodies.js";import{writeDocument as j}from"../graph/documents.js";import{asRuntimeWriter as B}from"../
|
|
1
|
+
var O=Object.defineProperty;var i=(e,t)=>O(e,"name",{value:t,configurable:!0});import{cancelCronsOnWake as q,getNode as E}from"../canvas/index.js";import{runStructuredRequestEffects as g,settleStructuredRequestIn as R}from"../canvas/structured-requests.js";import{afterCommit as x,openDb as F,withCanvasWrite as k}from"../canvas/db.js";import{brokerRelayGeneration as M,recordBrokerRelease as W}from"../runtime/lifecycle.js";import{randomUUID as z}from"node:crypto";import{composeNodeOutcomeDocument as A}from"../runtime/outcome-document.js";import{closeNode as L}from"../runtime/close.js";import{put as H}from"../graph/bodies.js";import{writeDocument as j}from"../graph/documents.js";import{asRuntimeWriter as B}from"../permissions/ceiling.js";import{publish as T}from"../graph/events.js";import{nodeObject as v}from"../graph/objects.js";import{readerForNode as J}from"../graph/reader.js";import{endMessageSubscriptions as C}from"../graph/watches.js";import{operationIdContext as D}from"../events/operation-id.js";class b extends Error{static{i(this,"StructuredRequestFinalizationError")}state;constructor(t){super(t==="expired"?"structured_request_expired":"structured_request_settled"),this.state=t}}class h extends Error{static{i(this,"FinalizationError")}code;report;constructor(t,r){super(t),this.code=t,this.report=r}}function Y(e){const t=i((r,n=2)=>String(r).padStart(n,"0"),"pad");return`${e.getUTCFullYear()}${t(e.getUTCMonth()+1)}${t(e.getUTCDate())}T${t(e.getUTCHours())}${t(e.getUTCMinutes())}${t(e.getUTCSeconds())}${t(e.getUTCMilliseconds(),3)}`}i(Y,"compactTs");function S(e,t=80){const r=e.split(`
|
|
2
2
|
`)[0]??"";return r.length>t?`${r.slice(0,t-1)}\u2026`:r}i(S,"firstLine");function P(e){return e.kind==="final"?{event:"report.final"}:e.result!==void 0?{event:"report.result"}:e.kind==="urgent"?{event:"report.update",level:7}:{event:"report.update",level:e.deliveryTier==="deferred"?3:e.deliveryTier==="urgent"?7:5}}i(P,"eventFor");async function U(e,t,r){const n=r.from??t,o=`reports/${Y(new Date)}-${z().slice(-8)}-${r.kind}`,u=await v(e,t),c=await v(e,n),{object:d}=await j(e,B(await J(e,n,async()=>[])),{owner:u,kind:"report",name:o,preview:S(r.body)||r.kind,privacy:"public"},r.body),s=`${t}/${o}`,{event:p,level:m}=P(r);await T(e,{event:p,object:u,actor:c,...m===void 0?{}:{level:m},attrs:{},pointer:d.id,messageBody:{kind:r.kind,label:S(r.body),bodyRef:s,...r.disposition===void 0?{}:{disposition:r.disposition}}});const l=(await e.prepare(`SELECT d.destination_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
3
3
|
JOIN objects receiver ON receiver.id = d.destination_id
|
|
4
4
|
WHERE m.body_ref = ? AND receiver.type = 'node' ORDER BY d.destination_id`).all(s)).map(w=>w.destination_id);return r.kind==="final"?await C(e,u,"final"):r.result?.requestMessageId&&await C(e,u,{result:r.result.requestMessageId}),{name:o,ref:s,doc:d,createdAt:d.created_at,deliveredTo:l}}i(U,"writeReport");async function G(e,t){return k(async r=>{const n=await r.prepare("SELECT status, final_report FROM nodes WHERE node_id = ?").get(e);if(n===void 0)throw new h("unknown_node");const o=t.outcome;let u;if(o?.structured!==void 0){const a=await R(r,o.structured.requestId,o.structured.declined===void 0?{intended:"completed",result:o.structured.value}:{intended:"declined",declined:o.structured.declined});if(!a.changed||a.row===null||a.row.state!=="completed"&&a.row.state!=="declined")return{written:void 0,...a.row===null?{}:{structured:a.row},structuredRefused:!0};u=a.row}else{const a=await r.prepareUncached("SELECT request_id FROM structured_requests WHERE node_id = ? AND state = 'pending'").get(e);if(a!==void 0){const f=await R(r,a.request_id,{intended:"failed",failureReason:"finalized"});if(!f.changed||f.row===null||f.row.state!=="failed")return{written:void 0,...f.row===null?{}:{structured:f.row},structuredRefused:!0};u=f.row}}if(n.final_report!==null)throw new h("already_finalized",`${e}/${n.final_report}`);if(n.status!=="active"&&n.status!=="idle"&&n.status!=="done")throw new h("finalization_forbidden");const c=await U(r,e,t),d=await E(e),s=d?.kind!=="human",p=(d?.outcome_revision??0)+1,m=t.outcome?.kind??"result",l=t.outcome?.reason??"finalized",w=t.outcome?.detailJson??null,_=c.createdAt;if(s){const a=d===null?null:await A({node:{...d,status:"done",intent:"done",terminal_reason:l},outcome:{kind:m,reason:l,at:_,revision:p,detailJson:w},finalReport:c.name,...u?.state==="completed"?{structuredResult:u.result}:{}});await r.prepare("UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = ?, final_report = ?, finalized_at = ?, outcome_kind = ?, outcome_reason = ?, outcome_at = ?, outcome_revision = outcome_revision + 1, outcome_detail_json = ? WHERE node_id = ? AND final_report IS NULL").run(l,c.name,_,m,l,_,w,e),a!==null&&await T(r,{event:"outcome",object:await v(r,e),actor:null,attrs:{revision:p},messageBody:a})}else await r.prepare("UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = 'finalized', final_report = ?, finalized_at = ? WHERE node_id = ? AND final_report IS NULL").run(c.name,_,e);const N=M(e);return x(()=>W(e,N)),{written:c,...u===void 0?{}:{structured:u}}})}i(G,"commitCanonicalFinal");async function $(e,t){if(!e)throw new Error(`push: empty nodeId (kind=${t.kind}) \u2014 a report must belong to a node`);if(t.kind==="final"&&t.deliveryTier!==void 0)throw new Error("push final does not accept a delivery tier");if(t.kind!=="final"&&t.outcome!==void 0)throw new Error("push outcome is final-only");if(t.kind!=="update"&&t.result!==void 0)throw new Error("a one-off result is an update report");if(H(t.body),t.kind==="final"){const n=await G(e,t);if(n.structuredRefused===!0)throw n.structured!==void 0&&g(n.structured),new b(n.structured?.state??"failed");n.structured!==void 0&&t.outcome?.structured?.skipEffects!==!0&&g(n.structured);const{written:o}=n;if(await q(e),(await E(e))?.run_id===e){const c=await(await F()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ? ORDER BY created DESC").all(e,e);for(const d of c){const s=await E(d.node_id);s&&s.status!=="done"&&s.status!=="canceled"&&s.status!=="dead"&&await L(d.node_id,{preserveEmpty:!0})}}return{report:o.ref,reportId:o.doc.id,deliveredTo:o.deliveredTo}}const r=await k(async n=>await U(n,e,t));return{report:r.ref,reportId:r.doc.id,deliveredTo:r.deliveredTo}}i($,"pushWithContext");function y(e,t){return D.current()===void 0?D.fresh(()=>$(e,t)):$(e,t)}i(y,"push");async function ce(e,t,r){return y(e,{kind:"update",body:t,...r})}i(ce,"pushUpdate");async function le(e,t,r){return y(e,{kind:"urgent",body:t,...r})}i(le,"pushUrgent");async function fe(e,t,r){return y(e,{kind:"final",body:t,...r})}i(fe,"pushFinal");export{h as FinalizationError,b as StructuredRequestFinalizationError,y as push,fe as pushFinal,ce as pushUpdate,le as pushUrgent};
|
|
@@ -101,7 +101,15 @@ export declare function hasSyncedGrants(source: string): Promise<boolean>;
|
|
|
101
101
|
* grant exists until grant sync installs the directory's mirror. */
|
|
102
102
|
export declare function dropLocalGrantMirror(db: CanvasDb): Promise<number>;
|
|
103
103
|
/** The directory response is complete; commit it or keep the prior snapshot intact. */
|
|
104
|
-
|
|
104
|
+
/** A directory team the runtime's owner belongs to: `team` is `team:<id>`, `members` are `user:<id>`. */
|
|
105
|
+
export interface TeamRecord {
|
|
106
|
+
team: string;
|
|
107
|
+
members: string[];
|
|
108
|
+
}
|
|
109
|
+
/** The `teams` a grant sync answer carried; absent (an unjoined install, an older directory) reads as none. */
|
|
110
|
+
export declare function teamsOf(value: unknown): TeamRecord[];
|
|
111
|
+
/** Replace the mirror with `grants` and the `written_by = 'directory'` team `member` facts with `teams`, in one transaction. */
|
|
112
|
+
export declare function replaceGrants(grants: readonly GrantRecord[], syncedAt: string, source: string, teams?: readonly TeamRecord[]): Promise<void>;
|
|
105
113
|
/** Bumped by every `replaceGrants`. A launch compiled against one generation
|
|
106
114
|
* and started under another may hold mounts the new mirror no longer grants. */
|
|
107
115
|
export declare function grantsGeneration(): Promise<number>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var m=Object.defineProperty;var t=(e,r)=>m(e,"name",{value:r,configurable:!0});import{createHash as S}from"node:crypto";import{parseScope as T}from"@north-light/identity";import{openDb as c,withCanvasWrite as O}from"../canvas/db.js";import{isUnsandboxedMachine as v}from"../runtime/unsandboxed.js";import{replaceWrittenBy as w,userSubject as x}from"../permissions/facts.js";import{GROUP_TYPE as R}from"../permissions/builtin.js";const A=["not_started","pending","ready","failed","unavailable"],N=["ultra","strong","medium","light"];function p(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(p,"entries");function h(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:n,next:i,step:o}=e;return typeof r!="string"||!A.includes(r)||n!==void 0&&typeof n!="string"||o!==void 0&&typeof o!="string"||typeof i!="string"||!i?null:{state:r,...n===void 0?{}:{message:n},next:i,...o===void 0?{}:{step:o}}}t(h,"dependencyOf");function y(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[n,i]of Object.entries(e)){const o=h(i);o&&(r[n]=o)}return r}t(y,"dependenciesOf");function b(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{base_url:r,rungs:n}=e;if(typeof r!="string"||!r||!n||typeof n!="object"||Array.isArray(n))return null;const i={};for(const o of N){const a=n[o];typeof a=="string"&&a&&(i[o]=a)}return Object.keys(i).length?{base_url:r.replace(/\/+$/,""),rungs:i}:null}t(b,"inferenceEndpointOf");function f(e){return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:p(JSON.parse(e.scopes_json)),resolved_scopes:p(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:y(JSON.parse(e.dependencies_json)),run_notice_url:e.run_notice_url??null,inference_endpoint:e.inference_endpoint_json?b(JSON.parse(e.inference_endpoint_json)):null,synced_at:e.synced_at}}t(f,"record");function u(e){if(!e||e.status!=="active")return null;try{return F(f(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(u,"active");function l(e){return e.grantee!=="app:terminal"&&v()}t(l,"mirrorOnly");async function I(){return(await L()).filter(e=>!l(e))}t(I,"localGrants");async function L(){return(await(await c()).prepare("SELECT * FROM grants").all()).flatMap(e=>{try{return[f(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(L,"allGrants");function j(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}t(j,"grantExpired");function F(e){if(e.status!=="active"||j(e))return null;try{const r=e.resolved_scopes.map(({scope:n})=>T(n));return{...e,resolved:e.resolved_scopes.flatMap(({mode:n},i)=>n==="always"?[r[i]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(F,"activeRecord");function d(e){return Array.isArray(e)?`[${e.map(d).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${d(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(d,"canonicalJson");function U(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{},...e.inference_endpoint?{inference_endpoint:e.inference_endpoint}:{}};return S("sha256").update(d(r)).digest("base64url").slice(0,22)}t(U,"grantVersion");async function $(e){return!!await(await c()).prepare("SELECT 1 FROM grants WHERE id = ?").get(e)}t($,"hasGrant");async function P(e){return await(await c()).prepare("SELECT rev, status FROM grants WHERE id = ?").get(e)??null}t(P,"grantSyncState");async function V(e,r){return await(await c()).prepare("SELECT rev, status FROM grants WHERE grantee = ? AND sub = ?").get(e,r)??null}t(V,"grantSyncStateFor");async function M(e){const r=await(await c()).prepare("SELECT * FROM grants WHERE id = ?").get(e);if(!r)return null;try{return f(r)}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return null;throw n}}t(M,"grantRecord");async function B(e){return(e?(await M(e))?.payer:null)??"user"}t(B,"payerOfGrant");async function X(e){return u(await(await c()).prepare("SELECT * FROM grants WHERE id = ?").get(e))}t(X,"activeGrant");async function Y(e,r){return u(await(await c()).prepare("SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(e,r))}t(Y,"grantFor");async function q(e){if(l({grantee:e}))return null;const r=await(await c()).prepare("SELECT * FROM grants WHERE grantee = ? AND status = 'active'").all(e);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return u(r[0])}t(q,"activeGrantForApp");async function z(e){const r=await(await c()).prepare("SELECT run_notice_url AS url, sub, grantee FROM grants WHERE id = ? AND status = 'active'").get(e);return r&&r.url&&!l(r)?{url:r.url,sub:r.sub,grantee:r.grantee}:null}t(z,"runNoticeTarget");async function K(){const e=await(await c()).prepare("SELECT * FROM grants WHERE grantee = 'app:terminal' AND status = 'active'").all();if(e.length>1)throw new Error("multiple active terminal grants");return u(e[0])}t(K,"terminalGrant");async function Q(){return(await(await c()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value??null}t(Q,"grantsSource");async function Z(e){return(await(await c()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value===e}t(Z,"hasSyncedGrants");async function ee(e){return(await e.prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value!=="local"?0:(await e.prepare("DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number((await e.prepare("DELETE FROM grants").run()).changes))}t(ee,"dropLocalGrantMirror");function re(e){if(e===void 0)return[];if(!Array.isArray(e))throw new TypeError("invalid grant sync response");return e.map(r=>{const{team:n,members:i}=r??{};if(typeof n!="string"||!/^team:\S+$/.test(n)||!Array.isArray(i)||i.some(o=>typeof o!="string"||!/^user:[^\s*]+$/.test(o)))throw new TypeError("invalid grant sync response");return{team:n,members:i}})}t(re,"teamsOf");async function te(e,r,n,i=[]){const o=new Set;for(const a of e){if(o.has(a.id)||!a.id||!a.runtime||!a.sub||!a.grantee||!Number.isInteger(a.rev)||!["active","revoked","removed"].includes(a.status))throw new TypeError("invalid grant sync response");o.add(a.id),p(a.scopes),p(a.resolved_scopes)}await O(async a=>{await a.exec("DELETE FROM grants");const E=a.prepare(`INSERT INTO grants
|
|
2
2
|
(id, runtime, sub, grantee, rev, scopes_json, resolved_scopes_json, spend_cap, expires_at,
|
|
3
3
|
status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, inference_endpoint_json, synced_at)
|
|
4
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const s of e)await
|
|
4
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const s of e)await E.run(s.id,s.runtime,s.sub,s.grantee,s.rev,JSON.stringify(s.scopes),JSON.stringify(s.resolved_scopes),s.spend_cap,s.expires_at,s.status,s.revoked_at,s.approved_at,s.payer,JSON.stringify(y(s.dependencies)),s.run_notice_url??null,s.inference_endpoint?JSON.stringify(s.inference_endpoint):null,s.synced_at);await w(a,"directory",R,"member",i.flatMap(({team:s,members:_})=>_.map(g=>({resourceId:s,subject:x(g)})))),await a.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(n),await a.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(runtime_state.value AS INTEGER) + 1 AS TEXT)").run()})}t(te,"replaceGrants");async function ne(){const e=await(await c()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(ne,"grantsGeneration");export{X as activeGrant,q as activeGrantForApp,F as activeRecord,L as allGrants,y as dependenciesOf,ee as dropLocalGrantMirror,j as grantExpired,Y as grantFor,M as grantRecord,P as grantSyncState,V as grantSyncStateFor,U as grantVersion,ne as grantsGeneration,Q as grantsSource,$ as hasGrant,Z as hasSyncedGrants,b as inferenceEndpointOf,I as localGrants,l as mirrorOnly,B as payerOfGrant,te as replaceGrants,z as runNoticeTarget,re as teamsOf,K as terminalGrant};
|
package/dist/core/grants/sync.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var S=Object.defineProperty;var r=(e,n)=>S(e,"name",{value:n,configurable:!0});import{createPrivateKey as E,randomUUID as j,sign as x}from"node:crypto";import{readFileSync as $}from"node:fs";import{join as T}from"node:path";import{crtrHome as A}from"../canvas/paths.js";import{isUnsandboxedMachine as g}from"../runtime/unsandboxed.js";import{emitEvent as p}from"../events/emit.js";import{describeOutboundFailure as
|
|
1
|
+
var S=Object.defineProperty;var r=(e,n)=>S(e,"name",{value:n,configurable:!0});import{createPrivateKey as E,randomUUID as j,sign as x}from"node:crypto";import{readFileSync as $}from"node:fs";import{join as T}from"node:path";import{crtrHome as A}from"../canvas/paths.js";import{isUnsandboxedMachine as g}from"../runtime/unsandboxed.js";import{emitEvent as p}from"../events/emit.js";import{describeOutboundFailure as O,outboundFetch as v,outboundJson as h,outboundRefusal as k}from"../events/outbound.js";import{allGrants as N,dependenciesOf as P,inferenceEndpointOf as R,replaceGrants as G,teamsOf as J}from"./mirror.js";import{onGrantSync as B}from"./on-sync.js";import{PersonUnresolvedError as D,personId as F,resolvePerson as M,retireLocalPerson as U,setPersonId as C}from"../canvas/person.js";const se=300;function I(e,n){const t=Math.floor(Date.now()/1e3),o=Buffer.from(JSON.stringify({alg:"ES256",typ:"JWT",kid:e.kid})).toString("base64url"),d=Buffer.from(JSON.stringify({iss:e.client_id,sub:e.client_id,aud:n,iat:t,exp:t+300,jti:j()})).toString("base64url"),i=`${o}.${d}`,s=x("sha256",Buffer.from(i),{key:E({key:e.jwk,format:"jwk"}),dsaEncoding:"ieee-p1363"});return`${i}.${s.toString("base64url")}`}r(I,"assertion");function y(){const e=JSON.parse($(T(A(),"secrets","runtime-client.json"),"utf8"));if(!e.jwk||e.alg!=="ES256"||!e.kid||!e.client_id||!e.runtime||e.jwk.kid!==e.kid||e.jwk.kty!=="EC"||e.jwk.crv!=="P-256"||!e.jwk.d)throw new TypeError("invalid runtime client key");return e}r(y,"runtimeClient");function ae(e=y()){return e.runtime.startsWith("runtime:")?e.runtime.slice(8):e.runtime}r(ae,"runtimeId");function L(e){return`This machine was removed from your Northlight account. To use it again, create a join token at ${e.replace(/\/$/,"")}/account/machines, and run \`crtr sys join\` from a plain terminal.`}r(L,"machineRevokedMessage");class w extends Error{static{r(this,"MachineRevokedError")}constructor(n){super(L(n)),this.name="MachineRevokedError"}}async function W(e,n){const t=y(),o=`${e.replace(/\/$/,"")}/token`,d=new URLSearchParams({grant_type:"client_credentials",client_id:t.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:I(t,o),...n===void 0?{}:{audience:n}}),i=await v("directory_token",o,{method:"POST",body:d,signal:AbortSignal.timeout(1e4)});if(!i.ok)throw i.status===401&&g()&&await q(i)?new w(e):k("directory_token",o,`directory token request failed: ${i.status}`);const s=await h("directory_token",o,i);if(typeof s.access_token!="string")throw new TypeError("directory token response has no access_token");return{access_token:s.access_token,expires_at:z(s.access_token,s.expires_in)}}r(W,"mintDirectoryToken");async function q(e){try{return(await e.json()).error==="invalid_client"}catch{return!1}}r(q,"isInvalidClient");function z(e,n){if(typeof n=="number"&&Number.isFinite(n)&&n>0)return Date.now()+n*1e3;try{const t=JSON.parse(Buffer.from(e.split(".")[1]??"","base64url").toString("utf8"));if(typeof t.exp=="number")return t.exp*1e3}catch{}return Date.now()+3e5}r(z,"tokenExpiry");function H(e,n){if(!e||typeof e!="object")throw new TypeError("invalid grant sync response");const t=e,o=n.runtime.startsWith("runtime:")?n.runtime:`runtime:${n.runtime}`;if(!(t.runtime===o||g()&&typeof t.runtime=="string"&&t.runtime.startsWith("runtime:")&&t.grantee!=="app:terminal")||typeof t.id!="string"||typeof t.sub!="string"||typeof t.grantee!="string"||!Number.isInteger(t.rev)||!Array.isArray(t.scopes)||!Array.isArray(t.resolved_scopes)||!["active","revoked","removed"].includes(t.status??"")||typeof t.synced_at!="string")throw new TypeError("invalid grant sync response");const s=e.revisions?.find(a=>a.rev===t.rev)?.approved_at;if(typeof s!="string")throw new TypeError("grant revision has no approval time");const l=t.payer==="app"?"app":"user";return{...t,spend_cap:t.spend_cap??null,expires_at:t.expires_at??null,revoked_at:t.revoked_at??null,approved_at:s,payer:l,dependencies:P(t.dependencies),inference_endpoint:l==="app"?R(t.inference_endpoint):null,run_notice_url:typeof t.run_notice_url=="string"&&t.run_notice_url!==""?t.run_notice_url:null}}r(H,"responseGrant");async function K(){try{F();return}catch(t){if(!(t instanceof D))throw t}const e=await M();if(e===null)return;C(e.id);const n=await U();p({level:"info",event:"daemon.person.resolved",fields:{person_id:e.id,source:e.source}}),p({level:"info",event:"daemon.person.retired",fields:{person_id:n.person,retired:n.retired,columns:n.columns}})}r(K,"resolveLatePerson");let _;function ce(e){return _?_(e):Promise.resolve()}r(ce,"requestGrantSync");function ue(e,n=t=>{}){let t;const o=e.replace(/\/$/,""),d=r(async()=>{const a=y(),c=await W(o),f=await v("grant_sync",`${o}/v1/runtime/grants`,{headers:{Authorization:`Bearer ${c.access_token}`},signal:AbortSignal.timeout(1e4)});if(!f.ok)throw k("grant_sync",`${o}/v1/runtime/grants`,`directory grant sync failed: ${f.status}`);const u=await h("grant_sync",`${o}/v1/runtime/grants`,f);if(!Array.isArray(u.grants)||typeof u.synced_at!="string")throw new TypeError("invalid grant sync response");const b=await N();await G(u.grants.map(m=>H(m,a)),u.synced_at,o,J(u.teams)),p({level:"info",event:"daemon.grants.synced",fields:{grants:u.grants.length,synced_at:u.synced_at}}),await K();try{await B(b,n)}catch(m){p({level:"error",event:"daemon.grants.on_sync_failed",error:m})}},"run");let i;const s=r(()=>{const a=d().catch(c=>{throw c instanceof w?p({level:"error",event:"daemon.grants.machine_revoked",error:c}):p({level:"error",event:"daemon.grants.sync_failed",error:c,fields:O(c)}),c}).finally(()=>{t===a&&(t=void 0)});return t=a,a},"start"),l=r((a={})=>t?a.fresh?(i??=t.catch(()=>{}).then(()=>(i=void 0,t??s())),i):t:s(),"sync");return _=l,l}r(ue,"createGrantSync");export{se as GRANT_SYNC_INTERVAL_S,w as MachineRevokedError,I as assertion,ue as createGrantSync,L as machineRevokedMessage,W as mintDirectoryToken,ce as requestGrantSync,y as runtimeClient,ae as runtimeId};
|
|
@@ -1,54 +1,13 @@
|
|
|
1
|
-
import type { ObjectRow, Reader } from './types.js';
|
|
2
1
|
import type { CanvasDb } from '../canvas/db.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* object and probes the chain, which made each read check cost milliseconds. */
|
|
6
|
-
export declare function ownerChain(db: CanvasDb, object: ObjectRow): Promise<ObjectRow[]>;
|
|
2
|
+
import type { ObjectRow, Reader } from './types.js';
|
|
3
|
+
/** The top of the object's owner chain (the object itself when it has no owner): its app, person or repo. */
|
|
7
4
|
export declare function spaceOf(db: CanvasDb, object: ObjectRow): Promise<ObjectRow>;
|
|
8
|
-
/** A shipped document: one a package reconciler owns, read from its package file
|
|
9
|
-
* (`documents.source_path` is set). Only the reconciler writes it. */
|
|
5
|
+
/** A shipped document: one a package reconciler owns, read from its package file (`documents.source_path` is set). Only the reconciler writes it. */
|
|
10
6
|
export declare function isShipped(db: CanvasDb, object: ObjectRow): Promise<boolean>;
|
|
11
|
-
/**
|
|
12
|
-
* installed, enabled plugin's tools are usable by every run, and an installed
|
|
13
|
-
* plugin is exactly an app holding live shipped documents. Hosted grants narrow
|
|
14
|
-
* this per reader later; `mayUseTools` is the one question both ask. */
|
|
15
|
-
export declare function toolApps(db: CanvasDb, reader: Reader): Promise<ObjectRow[]>;
|
|
16
|
-
/** May `reader` use any of `app`'s tool groups? v0: every run may use every installed plugin's tools. */
|
|
17
|
-
export declare function mayUseTools(_db: CanvasDb, _reader: Reader, app: ObjectRow): boolean;
|
|
18
|
-
/** Whether `scopes` hold a provider-group scope `<provider>:<group>` of `app`: what a run needs to call that app's provider commands
|
|
19
|
-
* (`requireGroup`). The provider id is the app's client id. */
|
|
20
|
-
export declare function holdsProviderGroup(db: CanvasDb, scopes: readonly string[], app: ObjectRow): Promise<boolean>;
|
|
21
|
-
/** Whether this reader may register, publish or watch as an object owned by `owner`. */
|
|
22
|
-
export declare function mayActAs(db: CanvasDb, reader: Reader, owner: ObjectRow): Promise<boolean>;
|
|
23
|
-
/** Run confinement for a node acting on a custom object of its app (Silas, 2026-09-30):
|
|
24
|
-
* a node reaches an object registered in its own run, or when neither run is isolated.
|
|
25
|
-
* An object the person or the app's server registered is reached by nodes outside
|
|
26
|
-
* isolation only. Any other reader is unchanged. */
|
|
27
|
-
export declare function customReachable(db: CanvasDb, reader: Reader, object: ObjectRow): Promise<boolean>;
|
|
28
|
-
/** The `crtr:objects:*` targets an object belongs to, from its owner chain (rulings R5):
|
|
29
|
-
* person space → `user`; under an app's profile → `profile:<id>`; a node of an app or a custom
|
|
30
|
-
* object → `app:<x>`; any node (local, or a stub of one of the account's nodes) → `node`.
|
|
31
|
-
* App documents and the profile itself stay under the memory scopes; a repo has no target.
|
|
32
|
-
* The app's own `app:<x>` is left out: its own objects need no scope. */
|
|
33
|
-
export declare function objectsTargets(db: CanvasDb, object: ObjectRow, appGrantee: string): Promise<string[]>;
|
|
34
|
-
/** Whether a non-person reader's scopes hold `crtr:objects:<verb>:<t>` for a target t of `object`.
|
|
35
|
-
* False for a person and for a revoked grant (`scopes === null`). `covers` is exact equality here. */
|
|
36
|
-
export declare function holdsObjectsScope(db: CanvasDb, reader: Reader, verb: 'read' | 'watch' | 'link' | 'write', object: ObjectRow): Promise<boolean>;
|
|
37
|
-
/** Whether the watch `watcher` → `object` that `app` made through `link` still authorizes delivering `object`'s events to `watcher` past the
|
|
38
|
-
* watcher's read gate: `app` owns `object` (directly, or through its profile: `mayActAs`) and, when its `scopes` are known, they hold
|
|
39
|
-
* `crtr:objects:link:<t>` for the watcher. Null `scopes` (a stub of a remote object: the home already checked the link) skip the scope. It grants
|
|
40
|
-
* delivery of this one object's events only; nothing about reading it. */
|
|
41
|
-
export declare function linkAuthorizes(db: CanvasDb, app: ObjectRow, scopes: readonly string[] | null, object: ObjectRow, watcher: ObjectRow): Promise<boolean>;
|
|
42
|
-
/** Callers turn `false` into `not_found`. A person reader reads everything. An app's
|
|
43
|
-
* shipped documents are read by any run that may use that app's tools. */
|
|
7
|
+
/** Callers turn `false` into `not_found`. */
|
|
44
8
|
export declare function canRead(db: CanvasDb, reader: Reader, object: ObjectRow): Promise<boolean>;
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
/** Always asked of the space, also for a person: a repo never owns a non-public
|
|
48
|
-
* document, whoever asks. A shipped document is changed by nobody, its own app
|
|
49
|
-
* included — only the package reconciler writes it, and it asks no reader. */
|
|
9
|
+
/** A shipped document is changed by nobody, its own app included: only the package reconciler writes it, and it asks no reader. A repo owns only
|
|
10
|
+
* public documents, so a non-public one under a repo is never changed, whoever asks. */
|
|
50
11
|
export declare function canChange(db: CanvasDb, reader: Reader, object: ObjectRow): Promise<boolean>;
|
|
51
|
-
/**
|
|
52
|
-
export declare function
|
|
53
|
-
/** The same for a document about to be created under `owner`. */
|
|
54
|
-
export declare function missingMemoryScopeUnder(db: CanvasDb, reader: Reader, owner: ObjectRow, access: 'read' | 'rw'): Promise<string | null>;
|
|
12
|
+
/** Whether a non-person reader reaches `object` through `crtr:objects:<verb>:<target>`: the ceiling for that verb, then the schema. A person holds no scope. */
|
|
13
|
+
export declare function holdsObjectsScope(db: CanvasDb, reader: Reader, verb: 'read' | 'watch' | 'link' | 'write', object: ObjectRow): Promise<boolean>;
|