@sigrun/sigrun 0.6.0 → 0.7.0
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/CHANGELOG.md +102 -0
- package/README.md +105 -18
- package/dist/blocks/build-stamp.json +2 -2
- package/dist/blocks/schema.js +5 -0
- package/dist/cli.js +85 -22
- package/dist/curation/host.js +46 -21
- package/dist/curation/migrate.js +96 -0
- package/dist/curation/overlays.js +52 -16
- package/dist/curation/paths.js +22 -0
- package/dist/curation/runs.js +34 -17
- package/dist/curation/skill-tool.js +34 -9
- package/dist/curation/store.js +382 -254
- package/dist/curation/targets.js +21 -0
- package/dist/curation/wrap.js +1 -6
- package/dist/extension.d.ts +12 -3
- package/dist/harness/boot.js +143 -6
- package/dist/harness/event-bus.js +19 -0
- package/dist/harness/loader.js +22 -0
- package/dist/harness/packages.js +7 -0
- package/dist/harness/reserved-sections.js +2 -0
- package/dist/harness/seam-host.js +71 -16
- package/dist/harness/workspace.js +34 -1
- package/dist/history/git.js +707 -0
- package/dist/history/trailers.js +145 -0
- package/dist/history/workspace-history.js +47 -0
- package/dist/jobs/index.js +5 -3
- package/dist/jobs/reconcile.js +2 -3
- package/dist/jobs/state-dir.js +9 -0
- package/dist/jobs/transfer.js +6 -6
- package/dist/sandbox/grants.js +11 -0
- package/dist/sandbox/launch.js +19 -9
- package/dist/sandbox/policy.js +6 -1
- package/dist/serve.js +3 -0
- package/dist/server/auth/proxy-only.js +5 -4
- package/dist/server/core-views/index.js +26 -0
- package/dist/server/core-views/jobs.js +661 -0
- package/dist/server/core-views/skills-tree.js +628 -0
- package/dist/server/core-views/skills.js +1090 -0
- package/dist/server/index.js +88 -0
- package/dist/server/jobs-access.js +62 -0
- package/dist/server/notifications.js +24 -12
- package/dist/server/routes/extensions.js +9 -1
- package/dist/server/routes/jobs.js +19 -52
- package/dist/server/routes/meta.js +9 -0
- package/dist/server/routes/onboarding.js +13 -1
- package/dist/server/surfaces/service.js +38 -0
- package/dist/skills/index.js +1 -1
- package/dist/skills/roots.js +301 -0
- package/node_modules/@sigrun/shared/CHANGELOG.md +13 -0
- package/node_modules/@sigrun/shared/dist/auth.d.ts +22 -0
- package/node_modules/@sigrun/shared/dist/auth.js +26 -0
- package/node_modules/@sigrun/shared/dist/blocks/index.d.ts +1 -1
- package/node_modules/@sigrun/shared/dist/blocks/types.d.ts +15 -0
- package/node_modules/@sigrun/shared/dist/model-label.d.ts +29 -0
- package/node_modules/@sigrun/shared/dist/model-label.js +55 -0
- package/node_modules/@sigrun/shared/dist/views.d.ts +28 -0
- package/node_modules/@sigrun/shared/package.json +9 -1
- package/package.json +7 -7
- package/webui/dist/assets/index-BI5k-Oci.js +55 -0
- package/webui/dist/assets/index-EGluyRXk.css +1 -0
- package/webui/dist/favicon.svg +7 -5
- package/webui/dist/index.html +3 -3
- package/webui/dist/assets/index-BR0di0OG.css +0 -1
- package/webui/dist/assets/index-BzrA1T4g.js +0 -55
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commit trailers for sigrun-owned git history (tabs+skills plan, Task 7).
|
|
3
|
+
*
|
|
4
|
+
* The feed reads action/skill from trailers, never by parsing the summary —
|
|
5
|
+
* the exact key set is the contract: Sigrun-Actor, Sigrun-Action,
|
|
6
|
+
* Sigrun-Skill, Sigrun-Review, Sigrun-Conversation, Sigrun-Reason,
|
|
7
|
+
* Sigrun-Undoes.
|
|
8
|
+
*
|
|
9
|
+
* Author identity rule (plan Global Constraints): author name = actor
|
|
10
|
+
* (`<user>` | `curator` | `agent` | `outside sigrun`), author email
|
|
11
|
+
* `<actor-slug>@sigrun.invalid` (`outside-sigrun@…`); the committer is always
|
|
12
|
+
* `sigrun <sigrun@sigrun.invalid>` (set by git.ts at commit time).
|
|
13
|
+
*/
|
|
14
|
+
export function actorName(actor) {
|
|
15
|
+
switch (actor.kind) {
|
|
16
|
+
case "user":
|
|
17
|
+
return actor.user;
|
|
18
|
+
case "curator":
|
|
19
|
+
return "curator";
|
|
20
|
+
case "agent":
|
|
21
|
+
return "agent";
|
|
22
|
+
case "outside":
|
|
23
|
+
return "outside sigrun";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function actorEmail(actor) {
|
|
27
|
+
let raw;
|
|
28
|
+
if (actor.kind === "user")
|
|
29
|
+
raw = actor.user;
|
|
30
|
+
else if (actor.kind === "outside")
|
|
31
|
+
raw = "outside-sigrun";
|
|
32
|
+
else
|
|
33
|
+
raw = actor.kind;
|
|
34
|
+
// The sigrun slug rule (src/curation/names.ts): lowercase, map every
|
|
35
|
+
// run of non-slug characters (spaces included) to one dash, trim edge
|
|
36
|
+
// dashes. A name that slugs to nothing falls back to "user".
|
|
37
|
+
const slug = raw
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
40
|
+
.replace(/^-+|-+$/g, "");
|
|
41
|
+
return `${slug === "" ? "user" : slug}@sigrun.invalid`;
|
|
42
|
+
}
|
|
43
|
+
export function decodeActor(value) {
|
|
44
|
+
if (value === "curator")
|
|
45
|
+
return { kind: "curator" };
|
|
46
|
+
if (value === "agent")
|
|
47
|
+
return { kind: "agent" };
|
|
48
|
+
if (value === "outside sigrun")
|
|
49
|
+
return { kind: "outside" };
|
|
50
|
+
return { kind: "user", user: value };
|
|
51
|
+
}
|
|
52
|
+
const TRAILER_KEY_LINE = /^Sigrun-(Actor|Action|Skill|Review|Conversation|Reason|Undoes):/;
|
|
53
|
+
/**
|
|
54
|
+
* Sanitize a string before it enters the commit message. Blank-line
|
|
55
|
+
* sequences collapse to a space: git's trailer block starts at the last
|
|
56
|
+
* blank-line-separated paragraph, so an embedded "\n\nSigrun-Actor: …"
|
|
57
|
+
* would otherwise decode as trusted metadata. Lines shaped like a trailer
|
|
58
|
+
* key are flattened into the text.
|
|
59
|
+
*/
|
|
60
|
+
export function sanitizeField(value) {
|
|
61
|
+
return value
|
|
62
|
+
.split("\n")
|
|
63
|
+
.map((line) => {
|
|
64
|
+
if (!TRAILER_KEY_LINE.test(line.trimStart()))
|
|
65
|
+
return line;
|
|
66
|
+
return line.replace(":", " -");
|
|
67
|
+
})
|
|
68
|
+
.join(" ")
|
|
69
|
+
.replace(/\s+/g, " ")
|
|
70
|
+
.trim();
|
|
71
|
+
}
|
|
72
|
+
/** Every CommitMeta string that flows into the commit message. */
|
|
73
|
+
export function sanitizeMeta(meta) {
|
|
74
|
+
const clean = (value) => value === undefined ? undefined : value.replace(/[\r\n]+/g, " ").trim();
|
|
75
|
+
const summary = sanitizeField(meta.summary);
|
|
76
|
+
return {
|
|
77
|
+
actor: meta.actor,
|
|
78
|
+
summary: summary === "" ? "(no summary)" : summary,
|
|
79
|
+
action: clean(meta.action),
|
|
80
|
+
skill: clean(meta.skill),
|
|
81
|
+
reviewId: clean(meta.reviewId),
|
|
82
|
+
conversationId: meta.conversationId,
|
|
83
|
+
reason: clean(meta.reason),
|
|
84
|
+
undoes: clean(meta.undoes),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function encodeTrailers(meta) {
|
|
88
|
+
const lines = [`Sigrun-Actor: ${actorName(meta.actor)}`];
|
|
89
|
+
if (meta.action !== undefined)
|
|
90
|
+
lines.push(`Sigrun-Action: ${meta.action}`);
|
|
91
|
+
if (meta.skill !== undefined)
|
|
92
|
+
lines.push(`Sigrun-Skill: ${meta.skill}`);
|
|
93
|
+
if (meta.reviewId !== undefined)
|
|
94
|
+
lines.push(`Sigrun-Review: ${meta.reviewId}`);
|
|
95
|
+
if (meta.conversationId !== undefined) {
|
|
96
|
+
lines.push(`Sigrun-Conversation: ${meta.conversationId}`);
|
|
97
|
+
}
|
|
98
|
+
if (meta.reason !== undefined)
|
|
99
|
+
lines.push(`Sigrun-Reason: ${meta.reason}`);
|
|
100
|
+
if (meta.undoes !== undefined)
|
|
101
|
+
lines.push(`Sigrun-Undoes: ${meta.undoes}`);
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
function trailer(message, key) {
|
|
105
|
+
const value = new RegExp(`^${key}:\\s*(.+)$`, "m").exec(message)?.[1]?.trim();
|
|
106
|
+
return value === undefined || value === "" ? undefined : value;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Decode one commit's metadata from `git show --format=fuller` output (the
|
|
110
|
+
* shape the tests produce with a plain `git show`; GitHistory.log parses its
|
|
111
|
+
* own record format instead). `paths` is always empty here — fuller output
|
|
112
|
+
* carries no diff.
|
|
113
|
+
*/
|
|
114
|
+
export function decodeCommit(raw) {
|
|
115
|
+
const sha = /^commit ([0-9a-f]{40})/m.exec(raw)?.[1];
|
|
116
|
+
if (sha === undefined)
|
|
117
|
+
throw new Error("decodeCommit: no commit header found");
|
|
118
|
+
const authorName = /^Author:\s+(.*?)\s+<[^>]+>/m.exec(raw)?.[1] ?? "";
|
|
119
|
+
const authorDate = /^AuthorDate:\s+(.+)$/m.exec(raw)?.[1]?.trim();
|
|
120
|
+
const messageStart = raw.indexOf("\n\n");
|
|
121
|
+
const message = messageStart === -1
|
|
122
|
+
? ""
|
|
123
|
+
: raw
|
|
124
|
+
.slice(messageStart + 2)
|
|
125
|
+
.split("\n")
|
|
126
|
+
.map((line) => (line.startsWith(" ") ? line.slice(4) : line))
|
|
127
|
+
.join("\n")
|
|
128
|
+
.replace(/\n+$/, "");
|
|
129
|
+
const summary = message.split("\n").find((line) => line.trim() !== "") ?? "";
|
|
130
|
+
const conversationRaw = trailer(message, "Sigrun-Conversation");
|
|
131
|
+
const conversationId = conversationRaw === undefined ? undefined : Number(conversationRaw);
|
|
132
|
+
return {
|
|
133
|
+
sha,
|
|
134
|
+
at: authorDate === undefined ? "" : new Date(authorDate).toISOString(),
|
|
135
|
+
summary,
|
|
136
|
+
actor: decodeActor(trailer(message, "Sigrun-Actor") ?? authorName),
|
|
137
|
+
action: trailer(message, "Sigrun-Action"),
|
|
138
|
+
skill: trailer(message, "Sigrun-Skill"),
|
|
139
|
+
reviewId: trailer(message, "Sigrun-Review"),
|
|
140
|
+
conversationId,
|
|
141
|
+
reason: trailer(message, "Sigrun-Reason"),
|
|
142
|
+
undoes: trailer(message, "Sigrun-Undoes"),
|
|
143
|
+
paths: [],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace skills history (tabs+skills plan, Task 10): the SECOND
|
|
3
|
+
* per-user repo — `<workspace>/skills` under git, caught up on every
|
|
4
|
+
* settled run with actor `agent` and the Sigrun-Conversation trailer.
|
|
5
|
+
*
|
|
6
|
+
* The git dir lives under the user's curation history root
|
|
7
|
+
* (`<userArea>/curation/history/workspace.git`), NEVER inside the work
|
|
8
|
+
* tree — the work tree is a live user directory the agent edits, so the
|
|
9
|
+
* layout itself (not an info/exclude line) keeps the repo out of the
|
|
10
|
+
* staged content. Pathspec `["."]` with `add --all --force` stages the
|
|
11
|
+
* whole skills tree, including deletions of tracked files whose pathspec
|
|
12
|
+
* root no longer exists on disk (Task 7's shipped "exists on disk OR
|
|
13
|
+
* tracked" staging rule).
|
|
14
|
+
*/
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { historyDir } from "../curation/paths.js";
|
|
17
|
+
/**
|
|
18
|
+
* The workspace skills repo layout: gitDir
|
|
19
|
+
* `<userArea>/curation/history/workspace.git`, workTree
|
|
20
|
+
* `<workspace>/skills`, pathspec `["."]`.
|
|
21
|
+
*/
|
|
22
|
+
export function workspaceRepoSpec(userArea, workspace) {
|
|
23
|
+
return {
|
|
24
|
+
gitDir: join(historyDir(userArea), "workspace.git"),
|
|
25
|
+
workTree: join(workspace, "skills"),
|
|
26
|
+
pathspec: ["."],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The user's workspace skills history, cached per (user, workspace
|
|
31
|
+
* path): a changed workspace path yields a NEW instance on the SAME
|
|
32
|
+
* gitDir, so the history follows the user across workspace moves — one
|
|
33
|
+
* log, one commit per tree. `workspace` undefined (the workspaceRoot
|
|
34
|
+
* rules' refusals) resolves to no history.
|
|
35
|
+
*/
|
|
36
|
+
export function workspaceHistoryFor(user, workspace, deps) {
|
|
37
|
+
if (workspace === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
// NUL-separated: no (user, workspace) pair can collide on a boundary.
|
|
40
|
+
const key = `${user}\0${workspace}`;
|
|
41
|
+
const cached = deps.cache.get(key);
|
|
42
|
+
if (cached !== undefined)
|
|
43
|
+
return cached;
|
|
44
|
+
const history = deps.create(workspace);
|
|
45
|
+
deps.cache.set(key, history);
|
|
46
|
+
return history;
|
|
47
|
+
}
|
package/dist/jobs/index.js
CHANGED
|
@@ -28,7 +28,7 @@ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
|
28
28
|
import { JobDoc, listRevisions, listRuns, pruneHistory, readJob, scanJobs, writeJobChange, } from "./docs.js";
|
|
29
29
|
import { fireJob } from "./fire.js";
|
|
30
30
|
import { reconcileJobs, } from "./reconcile.js";
|
|
31
|
-
import {
|
|
31
|
+
import { removeJobState, warnJobStateRemoval } from "./state-dir.js";
|
|
32
32
|
import { specHash } from "./spec.js";
|
|
33
33
|
/** Thrown when an API write targets a `file:`/`ext:` job id (409 `job_declared`). */
|
|
34
34
|
export class JobDeclaredError extends Error {
|
|
@@ -92,8 +92,7 @@ export function createJobsService(deps) {
|
|
|
92
92
|
await removeJobState(root, id);
|
|
93
93
|
}
|
|
94
94
|
catch (error) {
|
|
95
|
-
|
|
96
|
-
console.warn(`jobs: could not remove job state ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
95
|
+
warnJobStateRemoval("job state", root, id, error);
|
|
97
96
|
}
|
|
98
97
|
};
|
|
99
98
|
// `job.changed` observation (W4 Task 4): ONE commit subscription over
|
|
@@ -240,6 +239,9 @@ export function createJobsService(deps) {
|
|
|
240
239
|
// failure to its caller; it never rewrites or retires the job.
|
|
241
240
|
await removeJobState(stateRoot(record.owner), id);
|
|
242
241
|
},
|
|
242
|
+
jobStateRoot(owner) {
|
|
243
|
+
return stateRoot(owner);
|
|
244
|
+
},
|
|
243
245
|
async runNow(id) {
|
|
244
246
|
// ONE clock reading: the manual fire key and the run's fireAt must
|
|
245
247
|
// agree — the settle path re-derives the run-record key from
|
package/dist/jobs/reconcile.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
39
39
|
import { JobDoc, patchJob, scanJobs, writeJobChange, } from "./docs.js";
|
|
40
40
|
import { nextFireAt } from "./schedule.js";
|
|
41
|
-
import {
|
|
41
|
+
import { removeJobState, warnJobStateRemoval } from "./state-dir.js";
|
|
42
42
|
import { specHash } from "./spec.js";
|
|
43
43
|
import { createScheduleTask } from "./task.js";
|
|
44
44
|
const SCAN_PAGE = 500;
|
|
@@ -159,8 +159,7 @@ export async function reconcileJobs(deps, ctx = BACKGROUND_CONTEXT) {
|
|
|
159
159
|
await removeJobState(root, id);
|
|
160
160
|
}
|
|
161
161
|
catch (error) {
|
|
162
|
-
|
|
163
|
-
console.warn(`jobs: could not remove retired job state ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
162
|
+
warnJobStateRemoval("retired job state", root, id, error);
|
|
164
163
|
}
|
|
165
164
|
}
|
|
166
165
|
// ── Pass 2: records → tasks ─────────────────────────────────────
|
package/dist/jobs/state-dir.js
CHANGED
|
@@ -14,3 +14,12 @@ export async function removeJobState(root, jobId) {
|
|
|
14
14
|
return;
|
|
15
15
|
await rm(jobStateDir(root, jobId), { recursive: true, force: true });
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* The one best-effort removal warning (index delete, reconcile retire,
|
|
19
|
+
* transfer catch-path): the path the caller actually resolved, or the
|
|
20
|
+
* bare id when the selected owner has no jobs root.
|
|
21
|
+
*/
|
|
22
|
+
export function warnJobStateRemoval(what, root, jobId, error) {
|
|
23
|
+
const path = root === undefined ? jobId : jobStateDir(root, jobId);
|
|
24
|
+
console.warn(`jobs: could not remove ${what} ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
25
|
+
}
|
package/dist/jobs/transfer.js
CHANGED
|
@@ -17,10 +17,9 @@
|
|
|
17
17
|
* ownerless until the source is edited — spec §8.3's posture).
|
|
18
18
|
*/
|
|
19
19
|
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
20
|
-
import { join } from "node:path";
|
|
21
20
|
import { registerOwnershipParticipant } from "../server/ownership.js";
|
|
22
21
|
import { scanJobs, writeJobChange } from "./docs.js";
|
|
23
|
-
import {
|
|
22
|
+
import { warnJobStateRemoval } from "./state-dir.js";
|
|
24
23
|
import { specHash } from "./spec.js";
|
|
25
24
|
/**
|
|
26
25
|
* Register the jobs transfer participant on `ctx` (createServer calls
|
|
@@ -44,11 +43,12 @@ export function registerJobsOwnershipParticipant(ctx) {
|
|
|
44
43
|
await jobs.resetState(id);
|
|
45
44
|
}
|
|
46
45
|
catch (error) {
|
|
47
|
-
//
|
|
48
|
-
//
|
|
46
|
+
// Name the root resetState actually resolved (the workspace
|
|
47
|
+
// service's selection), never a guessed layout. (OS-user jobs
|
|
48
|
+
// cannot enter this transfer path — user deletion refuses
|
|
49
49
|
// osUser workspaces before any participant runs.)
|
|
50
|
-
const root =
|
|
51
|
-
|
|
50
|
+
const root = jobs.jobStateRoot(from);
|
|
51
|
+
warnJobStateRemoval("transferred job state", root, id, error);
|
|
52
52
|
}
|
|
53
53
|
const spec = { ...record.spec, owner: to };
|
|
54
54
|
const next = {
|
package/dist/sandbox/grants.js
CHANGED
|
@@ -131,6 +131,17 @@ export function validateGrantPath(path, ctx) {
|
|
|
131
131
|
(inheritedArea === undefined || !inside(candidate, inheritedArea))) {
|
|
132
132
|
return { ok: false, reason: "path is inside another user's area" };
|
|
133
133
|
}
|
|
134
|
+
// The curation dir is out of the agent's reach (spec §5): no
|
|
135
|
+
// workspace or grant may sit inside it, and none may CONTAIN it —
|
|
136
|
+
// a stored path equal to the bare user area would hand the agent
|
|
137
|
+
// the whole curation tree. Task 9 puts git history under
|
|
138
|
+
// curation/history.
|
|
139
|
+
for (const area of [ownArea, ...(inheritedArea === undefined ? [] : [inheritedArea])]) {
|
|
140
|
+
const curation = join(area, "curation");
|
|
141
|
+
if (inside(candidate, curation) || inside(curation, candidate)) {
|
|
142
|
+
return { ok: false, reason: "path overlaps the user's curation dir" };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
134
145
|
}
|
|
135
146
|
return { ok: true, realpath: candidate };
|
|
136
147
|
}
|
package/dist/sandbox/launch.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
/** The shared helper launch shape for worker and per-user service processes. */
|
|
2
|
+
/**
|
|
3
|
+
* Serialize a built policy into the `SIGRUN_SANDBOX_POLICY` env payload
|
|
4
|
+
* the sandbox helper parses (sandbox/src/policy.rs — `deny_unknown_fields`,
|
|
5
|
+
* so the field set here IS the wire contract). The single writer: every
|
|
6
|
+
* producer of a policy payload — `launchInJail`, the smoke-image jailed
|
|
7
|
+
* check — must build it here so the shapes cannot drift.
|
|
8
|
+
*/
|
|
9
|
+
export function serializePolicy(policy) {
|
|
10
|
+
return JSON.stringify({
|
|
11
|
+
ro: policy.ro,
|
|
12
|
+
rw: policy.rw,
|
|
13
|
+
ensureDirs: policy.ensureDirs,
|
|
14
|
+
minAbi: 6,
|
|
15
|
+
...(policy.uid === undefined ? {} : { uid: policy.uid }),
|
|
16
|
+
...(policy.gid === undefined ? {} : { gid: policy.gid }),
|
|
17
|
+
...(policy.groups === undefined ? {} : { groups: policy.groups }),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
2
20
|
/** Apply a complete policy in the helper, then exec the requested sibling process. */
|
|
3
21
|
export function launchInJail(helper, policy, command, args, env) {
|
|
4
22
|
return {
|
|
@@ -17,15 +35,7 @@ export function launchInJail(helper, policy, command, args, env) {
|
|
|
17
35
|
// is not granted inside the jail, so honoring it would be the
|
|
18
36
|
// same fatal startup error.
|
|
19
37
|
OPENSSL_CONF: `${policy.tmp}/openssl.cnf`,
|
|
20
|
-
SIGRUN_SANDBOX_POLICY:
|
|
21
|
-
ro: policy.ro,
|
|
22
|
-
rw: policy.rw,
|
|
23
|
-
ensureDirs: policy.ensureDirs,
|
|
24
|
-
minAbi: 6,
|
|
25
|
-
...(policy.uid === undefined ? {} : { uid: policy.uid }),
|
|
26
|
-
...(policy.gid === undefined ? {} : { gid: policy.gid }),
|
|
27
|
-
...(policy.groups === undefined ? {} : { groups: policy.groups }),
|
|
28
|
-
}),
|
|
38
|
+
SIGRUN_SANDBOX_POLICY: serializePolicy(policy),
|
|
29
39
|
},
|
|
30
40
|
};
|
|
31
41
|
}
|
package/dist/sandbox/policy.js
CHANGED
|
@@ -98,7 +98,12 @@ export function defaultModuleTrees(coreRoot) {
|
|
|
98
98
|
export function defaultPackageConfig(coreRoot) {
|
|
99
99
|
return join(coreRoot, "package.json");
|
|
100
100
|
}
|
|
101
|
-
|
|
101
|
+
/**
|
|
102
|
+
* One per-user path against the grant validator (Task 10 exports it:
|
|
103
|
+
* the workspace history's `workspaceRoot` resolves the SAME precedence
|
|
104
|
+
* as `buildUserPolicy` and must refuse the same paths).
|
|
105
|
+
*/
|
|
106
|
+
export function validateUserPath(path, record, managed, inheritedFrom) {
|
|
102
107
|
const candidate = canonical(path) ?? resolve(path);
|
|
103
108
|
if (inside(candidate, "/dev/shm") || inside("/dev/shm", candidate)) {
|
|
104
109
|
return { ok: false, reason: "path is or contains /dev/shm" };
|
package/dist/serve.js
CHANGED
|
@@ -228,6 +228,9 @@ export async function serve(env, options) {
|
|
|
228
228
|
extensionEntries: extensionResolutions,
|
|
229
229
|
env,
|
|
230
230
|
listenHost: host,
|
|
231
|
+
// Polish A1 (spec §5.2): serve goes on to createServer — bridge
|
|
232
|
+
// calls in the pre-bind window keep the bounded wait.
|
|
233
|
+
expectHostBridge: true,
|
|
231
234
|
});
|
|
232
235
|
// The gateway handle (gateway C2) exists BEFORE the server: the meta
|
|
233
236
|
// route reaches it through ServerContext, so the engine's health is
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
* deployments keep the setup-code/invite paths (token callers exist),
|
|
14
14
|
* and loopback binds keep them too (loopback is its own trust).
|
|
15
15
|
*/
|
|
16
|
+
import { isProxyOnly } from "@sigrun/shared/auth";
|
|
16
17
|
import { isLoopbackHost } from "./loopback.js";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
18
|
+
// Re-export: the predicate lives in @sigrun/shared (polish task 12 —
|
|
19
|
+
// one definition for the server, the webui, and the CLI); this module
|
|
20
|
+
// keeps the server's import path stable for its existing callers.
|
|
21
|
+
export { isProxyOnly };
|
|
21
22
|
/**
|
|
22
23
|
* Whether this boot requires a proxy vouch for account creation:
|
|
23
24
|
* proxy-only AND bound beyond loopback. The loopback test is the shared
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core-owned views (tabs+skills Task 4): the reserved `core` slug's
|
|
3
|
+
* built-in view surfaces (the Jobs and Skills views, attached by Tasks
|
|
4
|
+
* 5 and 12 via `SurfaceService.attachCoreViews`).
|
|
5
|
+
*
|
|
6
|
+
* Core views are NOT extensions: they have no outcome row, so the
|
|
7
|
+
* health dot is unaffected, and the `/v1/extensions` + `/v1/meta`
|
|
8
|
+
* routes list them under `coreViews`, never `extensions[]`. Rendering,
|
|
9
|
+
* data, actions, and instance-key/rev machinery run through the SAME
|
|
10
|
+
* `SurfaceService` code path as extension surfaces — this module only
|
|
11
|
+
* owns the wire projection.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The wire rows for a list of core-view defs: one {@link CoreViewInfo}
|
|
15
|
+
* per def, in attachment order (the channels list core views FIRST,
|
|
16
|
+
* ahead of extension views). `refreshMs` is sparse — absent when the
|
|
17
|
+
* def declares no cadence.
|
|
18
|
+
*/
|
|
19
|
+
export function coreViewInfos(defs) {
|
|
20
|
+
return defs.map((def) => ({
|
|
21
|
+
id: def.id,
|
|
22
|
+
title: def.title,
|
|
23
|
+
icon: def.icon,
|
|
24
|
+
...(def.refreshMs === undefined ? {} : { refreshMs: def.refreshMs }),
|
|
25
|
+
}));
|
|
26
|
+
}
|