@open-everywhere/create 0.1.4

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.
Files changed (82) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +69 -0
  4. package/bin/create-open-everywhere.js +57 -0
  5. package/package.json +57 -0
  6. package/src/cli.ts +241 -0
  7. package/src/deploy-probe.ts +99 -0
  8. package/src/engine.ts +106 -0
  9. package/src/env-file.ts +53 -0
  10. package/src/scaffold.ts +188 -0
  11. package/src/steps/build-deploy.ts +78 -0
  12. package/src/steps/database.ts +85 -0
  13. package/src/steps/env-checklist.ts +55 -0
  14. package/src/steps/index.ts +49 -0
  15. package/src/steps/prerequisites.ts +50 -0
  16. package/src/steps/repo.ts +69 -0
  17. package/src/steps/roster.ts +111 -0
  18. package/src/steps/slack.ts +74 -0
  19. package/src/steps/vercel.ts +40 -0
  20. package/src/steps/verify.ts +119 -0
  21. package/src/version.ts +20 -0
  22. package/template/.env.example +58 -0
  23. package/template/.mcp.json +8 -0
  24. package/template/AGENTS.md +62 -0
  25. package/template/CLAUDE.md +1 -0
  26. package/template/README.md +87 -0
  27. package/template/agent/access/access-requests.ts +69 -0
  28. package/template/agent/access/browser-hosts.ts +28 -0
  29. package/template/agent/access/bundles.ts +78 -0
  30. package/template/agent/access/connector-credentials.ts +74 -0
  31. package/template/agent/access/credential-bindings.generated.ts +17 -0
  32. package/template/agent/access/credential-bindings.ts +28 -0
  33. package/template/agent/access/grants.generated.ts +18 -0
  34. package/template/agent/access/grants.ts +30 -0
  35. package/template/agent/access/network-access.ts +32 -0
  36. package/template/agent/agent.ts +125 -0
  37. package/template/agent/blob/hosting.ts +42 -0
  38. package/template/agent/identity/authors.ts +79 -0
  39. package/template/agent/identity/groups-overlay.ts +24 -0
  40. package/template/agent/identity/groups.generated.ts +17 -0
  41. package/template/agent/identity/groups.ts +84 -0
  42. package/template/agent/identity/principals.ts +37 -0
  43. package/template/agent/identity/surfaces.ts +48 -0
  44. package/template/agent/instructions/access.ts +11 -0
  45. package/template/agent/instructions.md +295 -0
  46. package/template/agent/instrumentation.ts +6 -0
  47. package/template/agent/lib/memory-grants.ts +52 -0
  48. package/template/agent/lib/register-adapters.ts +286 -0
  49. package/template/agent/lib/register-config.ts +185 -0
  50. package/template/agent/memory/config/agents-overlay.ts +26 -0
  51. package/template/agent/memory/config/agents.generated.ts +21 -0
  52. package/template/agent/memory/config/domains/dm.ts +10 -0
  53. package/template/agent/memory/config/domains/index.ts +125 -0
  54. package/template/agent/memory/config/domains/org.ts +27 -0
  55. package/template/agent/memory/config/domains.ts +165 -0
  56. package/template/agent/model/models.ts +60 -0
  57. package/template/agent/model/policy.ts +19 -0
  58. package/template/agent/model/stacks.ts +75 -0
  59. package/template/agent/plugins/generated/index.ts +13 -0
  60. package/template/agent/plugins/registry.ts +16 -0
  61. package/template/agent/plugins/skill-content.ts +13 -0
  62. package/template/agent/roles/roles.ts +146 -0
  63. package/template/agent/routines/collectors.ts +89 -0
  64. package/template/agent/sandbox/backend-select.ts +16 -0
  65. package/template/agent/sandbox/sandbox.ts +242 -0
  66. package/template/agent/sandbox/workspace/.keep +6 -0
  67. package/template/agent/settings/settings.ts +19 -0
  68. package/template/agent/spend/budgets.ts +25 -0
  69. package/template/agent/spend/checkpoints.ts +51 -0
  70. package/template/agent/spend/model-pricing.ts +25 -0
  71. package/template/agent/webhooks/hooks-overlay.ts +34 -0
  72. package/template/agent/webhooks/hooks.generated.ts +17 -0
  73. package/template/agent/webhooks/hooks.ts +133 -0
  74. package/template/claude/skills/quickstart-open-everywhere/SKILL.md +167 -0
  75. package/template/claude/skills/upgrade-open-everywhere/SKILL.md +160 -0
  76. package/template/gitignore +42 -0
  77. package/template/package.json +62 -0
  78. package/template/roster.config.ts +39 -0
  79. package/template/roster.lock.json +182 -0
  80. package/template/scripts/env-pipe.mjs +74 -0
  81. package/template/scripts/ops-mcp.ts +35 -0
  82. package/template/tsconfig.json +23 -0
@@ -0,0 +1,125 @@
1
+ // The eve agent definition — the root of this Open Everywhere app.
2
+ //
3
+ // eve discovers the agent graph by filesystem convention from this directory. The slot directories
4
+ // (agent/tools/, agent/channels/, agent/hooks/, …) are `oe build` output, generated from
5
+ // roster.config.ts + roster.lock.json, and are not committed.
6
+ //
7
+ // Models run through the Vercel AI Gateway. Auth is to the gateway: Vercel OIDC when deployed,
8
+ // `AI_GATEWAY_API_KEY` (or `vercel env pull`) for local dev. There is no model-provider key in the
9
+ // app. The model per ROLE lives in agent/model/stacks.ts; pick a stack with `models.stack` in
10
+ // agent/settings/settings.ts.
11
+ //
12
+ // Zero data retention and provider allowlists are GATEWAY org policy (AI Gateway → Settings), not
13
+ // app code. This app inherits them.
14
+
15
+ import { defineAgent } from "eve";
16
+ import { defineDynamic } from "eve/tools";
17
+ // Side effect: register this app's config into core's registry before any handler runs.
18
+ import "./lib/register-config.js";
19
+ import "./lib/register-adapters.js";
20
+ // Side effect: refuse to boot on an eve whose loaded bytes do not carry the Open Everywhere patch.
21
+ // `npm install` applies it (the `postinstall` script runs `oe-eve-patch`).
22
+ import "@open-everywhere/core/agent/access/eve-patch-probe-boot.js";
23
+ import { gatewayModelResolver } from "@open-everywhere/adapters/agent/model/gateway.js";
24
+ import { registerGatewayDreamJudge, registerGatewayFailureFacetJudge } from "@open-everywhere/adapters/agent/model/dream-judge.js";
25
+ import { registerGatewayApprovalJudge } from "@open-everywhere/adapters/agent/model/approval-judge.js";
26
+ import { registerGatewayImageDescriber } from "@open-everywhere/adapters/agent/model/gateway-vision.js";
27
+ import { isModelUsable } from "@open-everywhere/core/agent/model/policy.js";
28
+ import {
29
+ JUDGE_MODEL,
30
+ PRIMARY_MODEL,
31
+ PRIMARY_MODEL_CONTEXT_WINDOW_TOKENS,
32
+ UTILITY_MODEL,
33
+ VISION_MODEL,
34
+ } from "./model/models.js";
35
+ import { loadModelPolicy } from "./model/policy.js";
36
+ import { compactionThresholdTokens } from "./model/stacks.js";
37
+ import { registerAppInfo } from "@open-everywhere/core/agent/commands/core.js";
38
+ import { getSettings } from "@open-everywhere/core/agent/settings/settings.js";
39
+ import { EVE_RUNTIME_VERSION } from "@open-everywhere/core/agent/channels/runtime-rotation.js";
40
+ // A static JSON import: the bundler inlines it at build time, so the version survives a bundled
41
+ // deploy that ships no package.json.
42
+ import appPackageJson from "../package.json" with { type: "json" };
43
+
44
+ /** Gateway options that are a property of the call. `caching: "auto"` injects prompt-cache breakpoints. */
45
+ const GATEWAY_DEFAULTS = { caching: "auto" } as const;
46
+
47
+ const SESSION_INPUT_TOKEN_BUDGET = getSettings().agent.sessionInputTokenBudget;
48
+ const COMPACTION_THRESHOLD_PERCENT = getSettings().agent.compactionThresholdPercent;
49
+ /** Display only (`/oe status`): eve derives its own copy from the two values the agent states. */
50
+ const COMPACTION_THRESHOLD_TOKENS = compactionThresholdTokens(PRIMARY_MODEL_CONTEXT_WINDOW_TOKENS, COMPACTION_THRESHOLD_PERCENT);
51
+
52
+ // The background judges: memory distillation, failure facets and the auto-approval judge.
53
+ registerGatewayDreamJudge({ id: UTILITY_MODEL, gateway: GATEWAY_DEFAULTS });
54
+ registerGatewayFailureFacetJudge({ id: UTILITY_MODEL, gateway: GATEWAY_DEFAULTS });
55
+ registerGatewayApprovalJudge({ id: JUDGE_MODEL, gateway: GATEWAY_DEFAULTS });
56
+
57
+ // The image describer. Fail at boot if the org model policy forbids the vision model, rather than
58
+ // fail every image later.
59
+ if (!isModelUsable(VISION_MODEL, loadModelPolicy())) {
60
+ throw new Error(
61
+ `open-everywhere: vision model "${VISION_MODEL}" is not allowed by the org model policy — ` +
62
+ "check agent/model/policy.ts, or re-point the `vision` role in agent/model/stacks.ts.",
63
+ );
64
+ }
65
+ registerGatewayImageDescriber({ id: VISION_MODEL });
66
+
67
+ function readAppVersion(): string {
68
+ const version = appPackageJson.version;
69
+ return typeof version === "string" && version.length > 0 ? version : "unknown";
70
+ }
71
+
72
+ function readDeploymentSha(): string {
73
+ const sha = (process.env.VERCEL_GIT_COMMIT_SHA ?? "").trim();
74
+ return sha.length > 0 ? sha : "unknown";
75
+ }
76
+
77
+ function readEvePin(): string {
78
+ const version = EVE_RUNTIME_VERSION.trim();
79
+ return version.length > 0 ? version : "unknown";
80
+ }
81
+
82
+ // What `/oe status` reports about this deployment.
83
+ registerAppInfo({
84
+ version: readAppVersion(),
85
+ primaryModel: PRIMARY_MODEL,
86
+ utilityModel: UTILITY_MODEL,
87
+ contextWindowTokens: PRIMARY_MODEL_CONTEXT_WINDOW_TOKENS,
88
+ compactionThresholdTokens: COMPACTION_THRESHOLD_TOKENS,
89
+ deploymentSha: readDeploymentSha(),
90
+ evePin: readEvePin(),
91
+ });
92
+
93
+ /**
94
+ * The turn model is DYNAMIC (per-domain routing), so eve takes the context window and the provider
95
+ * options from the resolver's return, not from the agent definition. Wrap every return at ONE site,
96
+ * so no routing path can drop the window — a missing window silently costs the compaction rescale.
97
+ */
98
+ function primaryModelSelection<TModel>(model: TModel): {
99
+ readonly model: TModel;
100
+ readonly modelContextWindowTokens: number;
101
+ readonly modelOptions: { readonly providerOptions: { readonly gateway: typeof GATEWAY_DEFAULTS } };
102
+ } {
103
+ return {
104
+ model,
105
+ modelContextWindowTokens: PRIMARY_MODEL_CONTEXT_WINDOW_TOKENS,
106
+ modelOptions: { providerOptions: { gateway: GATEWAY_DEFAULTS } },
107
+ };
108
+ }
109
+
110
+ export default defineAgent({
111
+ // just-bash runs in-process for the self-host sandbox tier; keep it out of the bundle.
112
+ build: { externalDependencies: ["just-bash"] },
113
+ model: defineDynamic({
114
+ events: {
115
+ "step.started": (() => {
116
+ const resolve = gatewayModelResolver({ id: PRIMARY_MODEL, gateway: GATEWAY_DEFAULTS });
117
+ return async (event: unknown, ctx: unknown) => primaryModelSelection(await resolve(event, ctx));
118
+ })(),
119
+ },
120
+ }),
121
+ reasoning: "medium",
122
+ // Sessions park on a token budget, never on wall-clock time.
123
+ limits: { maxInputTokensPerSession: SESSION_INPUT_TOKEN_BUDGET, sessionTimeoutMs: false },
124
+ compaction: { thresholdPercent: COMPACTION_THRESHOLD_PERCENT },
125
+ });
@@ -0,0 +1,42 @@
1
+ // Blob hosting — config-as-code, the org-level opt-in for PUBLISHING HTML artifacts to a
2
+ // world-readable Vercel Blob URL from the post_file tool (packages/core/agent/tools/post_file.ts).
3
+ //
4
+ // OFF BY DEFAULT. With `enabled:
5
+ // false` — the shipped default — post_file NEVER hosts: an HTML artifact is attached to the
6
+ // message as a downloadable `.html` file (workspace-scoped, no world-readable URL). Flip
7
+ // `enabled: true` to opt this org IN; then post_file publishes HTML pages to a public Blob URL,
8
+ // and that public write is APPROVAL-GATED at the tool (an admin approves each publish).
9
+ //
10
+ // TO TURN IT ON, an operator:
11
+ // 1. Connects a Vercel Blob store to this project (Vercel populates VERCEL_OIDC_TOKEN +
12
+ // BLOB_STORE_ID automatically — the SDK authenticates via OIDC, NO BLOB_READ_WRITE_TOKEN).
13
+ // 2. Sets `enabled: true` here and merges it (reviewed git config, same PR-to-widen posture
14
+ // as model/policy.ts and spend/budgets.ts).
15
+ // If `enabled: true` ships without the Blob store connected, the first publish FAILS LOUD
16
+ // (missing OIDC env) rather than silently degrading — an opted-in-but-unconfigured deployment
17
+ // is a misconfig we surface, not hide.
18
+ //
19
+ // This file is the seam a DB/dashboard would swap behind. After editing:
20
+ // `npm run typecheck` plus a boot (assertBlobHostingValid runs at import and throws on a malformed
21
+ // `publicPrefix`).
22
+
23
+ import { defineBlobHosting, type BlobHostingConfig } from "@open-everywhere/core/agent/blob/hosting.js";
24
+
25
+ const config: BlobHostingConfig = defineBlobHosting({
26
+ // OFF by default — HTML artifacts are attached as downloadable files, nothing is published.
27
+ enabled: false,
28
+
29
+ // --- EXAMPLE: opt this org in to hosting HTML artifacts on Vercel Blob ---
30
+ // Requires a connected Blob store (VERCEL_OIDC_TOKEN + BLOB_STORE_ID). Each publish is
31
+ // approval-gated at the post_file tool.
32
+ // enabled: true,
33
+
34
+ // --- OPTIONAL: override the reserved public-output prefix (namespace isolation) ---
35
+ // Defaults to "public-artifacts". A plain prefix segment: no leading/trailing slash, no "..".
36
+ // publicPrefix: "public-artifacts",
37
+ });
38
+
39
+ /** The seam a DB/dashboard would swap behind. Static config for now. */
40
+ export function loadBlobHosting(): BlobHostingConfig {
41
+ return config;
42
+ }
@@ -0,0 +1,79 @@
1
+ // Config-as-code: WHOSE requests this operator treats as an org member's, on the platforms that
2
+ // cannot answer that question themselves. The AUTHORITY plane — the sibling of ./surfaces.ts, which
3
+ // is the AUDIENCE plane.
4
+ //
5
+ // THE TWO ARE ADDITIVE, AND YOU NEED BOTH:
6
+ //
7
+ // surfaces.ts declares a ROOM. "Who can SEE what the agent prints here?" → trust: internal/public
8
+ // this file declares an AUTHOR. "Whose bytes may the agent treat as a member's?"
9
+ //
10
+ // Declaring an author internal does NOT make the room safe to print into, and declaring a room
11
+ // internal does NOT vouch for everyone who types in it. A public community guild with three trusted
12
+ // staff is exactly the case that needs both files.
13
+ //
14
+ // WHY IT EXISTS. The taint plane's author leg reads a POSITIVE refutation from the platform: Slack
15
+ // answers with `is_restricted`/`is_ultra_restricted`/`is_stranger`, Teams with Entra's
16
+ // `userType: "guest"`. DISCORD AND NOTION HAVE NO SUCH SIGNAL — Discord has no organisation concept
17
+ // (guild membership IS the boundary, and community guilds admit the world), and Notion exposes no
18
+ // membership fact on a comment author. On those two platforms this file is the only author-grain
19
+ // evidence that exists.
20
+ //
21
+ // 🔴 ARMING IS PER TENANT AND EXPLICIT. A tenant with no entry here is UNDECLARED, and the leg does
22
+ // nothing there — behaviour is byte-identical to not having this file. Adding an entry ARMS that
23
+ // tenant: from then on, an author it does not name is graded EXTERNAL, and their messages mark the
24
+ // turn as carrying non-member content. So an entry is a real commitment, not a hint. List everyone
25
+ // who belongs, or leave the tenant out.
26
+ //
27
+ // SHIPS EMPTY. Never commit real member ids or team names here — they are operator data and this
28
+ // repo is open source. A deployer fills it in their own fork. Validated fail-loud at boot
29
+ // (registerAuthorTrust); a malformed key or a reference to a group that does not exist is a boot
30
+ // error, not a silently-ignored line.
31
+ //
32
+ // THREE WAYS TO NAME A MEMBER:
33
+ //
34
+ // "discord/000000000000000000" one ACCOUNT, by qualified principal key. The floor — no
35
+ // database, no IdP, nothing else switched on.
36
+ // { group: "payments-team" } one PERSON-GROUP from ./groups.ts, expanded at boot to
37
+ // its principal keys on this platform. Link-set members do
38
+ // NOT count here (expanding one is an async DB read and
39
+ // this resolver runs on the synchronous wake path) — a
40
+ // group with no principal key on this platform is refused
41
+ // loudly rather than accepted and silently inert.
42
+ // { membership: "discord/<g>/role:<r>" } EVERY author holding that Discord role — the wildcard
43
+ // shape. A single trailing "*" is allowed, so
44
+ // "role:*" admits every author holding ANY role, which is
45
+ // the usual "has been let in properly" test on a community
46
+ // guild. Discord roles ride on the gateway message itself,
47
+ // so this costs no extra API call. Notion has no group
48
+ // concept, so a Notion entry uses principal keys only.
49
+ //
50
+ // 🔴 THE IdP IS A FUTURE SOURCE, AND IT PLUGS IN THROUGH `{ group: … }` — NOT THROUGH A LIVE READ.
51
+ // identity/groups-codemod.ts already emits `directory:<idp-group>` groups into the machine-owned
52
+ // overlay agent/identity/groups.generated.ts from a directory snapshot, on a bot PR that A HUMAN
53
+ // MERGES. Reference one of those groups here and Okta becomes the source with no new plumbing and
54
+ // no new trust: the IdP PROPOSES, a reviewer disposes. A directory that starts lying still cannot
55
+ // widen author trust without a merged diff. Do not replace this with a runtime directory lookup.
56
+ //
57
+ // defineAuthorTrust({
58
+ // tenant: "discord/1300000000000000100", // the guild this declaration ARMS
59
+ // internal: [
60
+ // "discord/000000000000000000", // one account
61
+ // { group: "payments-team" }, // a person-group from ./groups.ts
62
+ // { group: "directory:Engineering" }, // an IdP-synced group (merged overlay)
63
+ // { membership: "discord/1300000000000000100/role:111111111111111111" }, // one role
64
+ // ],
65
+ // }),
66
+
67
+ import { defineAuthorTrust, registerAuthorTrust, type AuthorTrustEntry } from "@open-everywhere/core/agent/identity/author-trust.js";
68
+ import { loadGroups } from "./groups.js";
69
+
70
+ // Deliberately empty in the open-source tree (no committed operator ids — see the header).
71
+ // `defineAuthorTrust` is imported so the example above type-checks the moment it is uncommented.
72
+ void defineAuthorTrust;
73
+
74
+ export const authors: readonly AuthorTrustEntry[] = [];
75
+
76
+ // Self-registering side effect, matching ./surfaces.ts: agent/lib/register-config.ts imports this
77
+ // file for its effect, so nothing has to be threaded through the AppConfig object. Fail-loud — a
78
+ // malformed tenant key, an unknown group or a dead cross-platform member is a boot error.
79
+ registerAuthorTrust(authors, loadGroups());
@@ -0,0 +1,24 @@
1
+ // The PERSON-GROUPS OVERLAY loader — the runtime half of the IdP-group sync's review path (phase G.4).
2
+ // `groups.generated.ts` (next to this file) is a MACHINE-OWNED, DATA-ONLY overlay of `directory:`-
3
+ // namespaced person-groups whose members are link-set references, merged into the config-as-code
4
+ // baseline in groups.ts at load. Nothing edits it by hand in the normal flow: the deterministic
5
+ // codemod (core identity/groups-codemod.ts) emits it from a plan diffed against a fresh directory
6
+ // snapshot, and a human merges the diff — merge IS the approval, exactly like the grants overlay
7
+ // (agent/access/grants.ts).
8
+ //
9
+ // WHY a static import (not an fs read): Eve's bundler inlines the module into the deploy artifact, so
10
+ // the overlay can never be silently absent at runtime — a membership that merged is a membership
11
+ // that ships. Parsing is STRICT + fail-loud (parseGroupsFile): a malformed overlay refuses to boot
12
+ // rather than loading as "roughly right", and Vercel keeps the previous deployment live on a failed
13
+ // build/boot. This loader does not prove the `.ts` module is data. In this app the person who
14
+ // merges the codemod's PR is the only gate on that — see agent/access/grants.ts.
15
+
16
+ import { parseGroupsFile, type GroupsFile } from "@open-everywhere/core/agent/identity/groups-codemod.js";
17
+ import groupsOverlay from "./groups.generated.js";
18
+
19
+ const overlay: GroupsFile = parseGroupsFile(groupsOverlay);
20
+
21
+ /** The parsed person-groups overlay — merged into loadGroups() by groups.ts. */
22
+ export function loadGroupsOverlay(): GroupsFile {
23
+ return overlay;
24
+ }
@@ -0,0 +1,17 @@
1
+ // GENERATED + MACHINE-OWNED — DO NOT HAND-EDIT.
2
+ //
3
+ // The person-groups overlay: DATA ONLY. Written exclusively by the deterministic codemod
4
+ // (packages/core/agent/identity/groups-codemod.ts) on a bot-PR branch; the PR merge is the approval gate.
5
+ //
6
+ // The codemod re-emits this file from its own parsed content and refuses to rewrite it when the
7
+ // bytes differ, so a hand-edit stops the next machine PR. Before a merge, nothing checks that the
8
+ // file is still data only unless the repository runs its own check: the person who merges the PR
9
+ // is the gate. Read a PR to this file as code, because it is code.
10
+ //
11
+ // To change it by hand, edit only the data below and keep the layout exactly as emitted.
12
+
13
+ import type { GroupsFile } from "@open-everywhere/core/agent/identity/groups-codemod.js";
14
+
15
+ export default {
16
+ "groups": []
17
+ } satisfies GroupsFile;
@@ -0,0 +1,84 @@
1
+ // PERSON-GROUPS — config-as-code (the app-owned instance of the core identity/groups.ts model).
2
+ // Declare named sets of HUMANS, then bind a capability bundle to one at `group` scope in
3
+ // agent/access/bundles.ts: "everybody on the payments team gets this MCP, these skills and this
4
+ // connection in their DM", without one binding per person per platform.
5
+ //
6
+ // A GROUP GRANTS PEOPLE, NOT ROOMS. A `group` binding applies ONLY in each member's own DM (the
7
+ // personal-credential universe — access/resolver.ts's "two connector universes" rule). Channels keep
8
+ // surface/domain scoping; there is deliberately no way to say "this channel's bundle applies only to
9
+ // members of group X".
10
+ //
11
+ // TWO WAYS TO NAME A MEMBER:
12
+ // "slack/<team-id>/<member-id>" one ACCOUNT, by QUALIFIED principal key — platform, then the
13
+ // WORKSPACE on a tenant-scoped platform (Slack member ids are issued per
14
+ // workspace — OPE-309), then the id. Needs no database, no IdP and nothing
15
+ // else switched on — the route a 5-person team uses, on every code path.
16
+ // { linkSet: "<id>" } one HUMAN, by link-set id — expands at resolve time to every account they
17
+ // have CONFIRMED (link_sets_list in the control plane shows the ids). A
18
+ // `suggested` account never counts, so an IdP proposal can never widen a
19
+ // grant. NB: link-set membership is resolved on the AWAITABLE access path
20
+ // (core access/group-request.ts); prefer principal keys for anything that
21
+ // must be effective on every path.
22
+ //
23
+ // A bare unqualified id ("<member-id>") is REFUSED at load — it has no platform namespace and could
24
+ // never match; so is a Slack key missing its workspace ("slack/<member-id>"), for the same reason one
25
+ // rung down. So is an empty group, a duplicate member, and a duplicate group name. Being in
26
+ // SEVERAL groups is fine and expected.
27
+ //
28
+ // OPEN-SOURCE / SECURITY: never commit real member ids or team names here (they're operator data) —
29
+ // this ships EMPTY; a deployer fills it in their own fork. Validated fail-loud at config load
30
+ // (assertGroupsValid), and a binding naming a group that doesn't exist fails at load too
31
+ // (agent/access/bundles.ts).
32
+ //
33
+ // defineGroup({
34
+ // name: "payments-team", // the id every binding references — immutable
35
+ // title: "Payments", // display only
36
+ // members: [
37
+ // "slack/<team-id>/<member-id>", // one account (Slack: workspace-qualified)
38
+ // "discord/000000000000000000", // another account (same or different human)
39
+ // { linkSet: "1f0c…" }, // one human, all their confirmed accounts
40
+ // ],
41
+ // }),
42
+ //
43
+ // …then in agent/access/bundles.ts:
44
+ //
45
+ // defineBinding({ bundle: "payments-tools", scope: { level: "group", group: "payments-team" } }),
46
+
47
+ import {
48
+ assertGroupsValid,
49
+ assertReservedGroupNamespace,
50
+ defineGroup,
51
+ type Group,
52
+ } from "@open-everywhere/core/agent/identity/groups.js";
53
+ import { loadGroupsOverlay } from "./groups-overlay.js";
54
+
55
+ // Deliberately empty in the open-source tree (no committed operator ids — see the header). A
56
+ // deployer adds their teams here; `defineGroup` is imported so the example above type-checks the
57
+ // moment it's uncommented.
58
+ void defineGroup;
59
+
60
+ const groups: Group[] = [];
61
+
62
+ // The `directory:` namespace belongs to the machine-owned overlay below — a hand-written group may
63
+ // not use it, or the next sync plan would silently replace a diff a human approved.
64
+ assertReservedGroupNamespace(groups);
65
+
66
+ // --- The IdP-GROUP OVERLAY (directory sync, review mode) ---------------------
67
+ // agent/identity/groups.generated.ts is a MACHINE-OWNED, DATA-ONLY overlay of `directory:<idp-group>`
68
+ // groups whose members are link-set refs, emitted by the deterministic codemod from a directory
69
+ // snapshot and merged by a human (the `directory_groups_plan` control verb returns the file content;
70
+ // merging the diff IS the approval). It merges ADDITIVELY here: overlay names are namespaced so they
71
+ // can never collide with the hand-written groups above, and the SAME assertGroupsValid below covers
72
+ // overlay-derived config. Empty overlay ⇒ no-op.
73
+ const overlay = loadGroupsOverlay();
74
+ const mergedGroups: readonly Group[] = [...groups, ...overlay.groups];
75
+
76
+ // Validate once, at module load (fail loud): unique non-empty names, non-empty membership, every
77
+ // string member a QUALIFIED principal key (workspace rung and all on Slack), every link-set member a real id, no duplicate
78
+ // members. Runs on the MERGED result, so a bad overlay can never load half-checked.
79
+ assertGroupsValid(mergedGroups);
80
+
81
+ /** The seam a DB/dashboard would swap behind. Currently static config + the IdP-group overlay. */
82
+ export function loadGroups(): readonly Group[] {
83
+ return mergedGroups;
84
+ }
@@ -0,0 +1,37 @@
1
+ // Cross-surface principals — the people on your team and their account id on each surface, so the
2
+ // agent knows that one person in Slack is the same person on GitHub.
3
+ //
4
+ // FAIL-CLOSED: an account joins a person ONLY by an explicit id below, never by a matching display
5
+ // name. An unlisted account stays a separate per-surface identity, and everything still works.
6
+ // Validated at config load (assertPrincipalsValid).
7
+ //
8
+ // definePrincipal({
9
+ // name: "Ada Lovelace",
10
+ // handle: "ada",
11
+ // ids: { slack: "<member-id>", github: "ada-gh", notion: "<uuid>" },
12
+ // email: "ada@example.com",
13
+ // }),
14
+ //
15
+ // A Slack member id is issued PER WORKSPACE. To serve two workspaces, name the workspace on each id:
16
+ //
17
+ // ids: { slack: [{ tenant: "<first-team-id>", id: "<member-id>" }, { tenant: "<second-team-id>", id: "<member-id>" }] }
18
+ //
19
+ // Do not mix bare and workspace-qualified ids for one platform — config load refuses it.
20
+
21
+ import { definePrincipal, assertPrincipalsValid, type Principal } from "@open-everywhere/core/agent/identity/principals.js";
22
+
23
+ // Deliberately empty in the open-source tree (no committed operator ids — see the header). A
24
+ // deployer adds their team here; `definePrincipal` is imported so the example above type-checks the
25
+ // moment it's uncommented.
26
+ void definePrincipal;
27
+
28
+ const principals: Principal[] = [];
29
+
30
+ // Validate once at module load (fail loud): no (platform, id) claimed by two people (the merge
31
+ // conflict), no empty names/ids, no duplicate emails (identity/principals.ts assertPrincipalsValid).
32
+ assertPrincipalsValid(principals);
33
+
34
+ /** The seam a DB/dashboard would swap behind. Static config for now. */
35
+ export function loadPrincipals(): readonly Principal[] {
36
+ return principals;
37
+ }
@@ -0,0 +1,48 @@
1
+ // The surfaces this deployment vouches as INTERNAL — allowed to read and write the shared org
2
+ // brain. Fail-closed: a surface with no entry stays `external`, and an external room routes to the
3
+ // read-only `public` pseudo-domain whatever domain lists it.
4
+ //
5
+ // A key is a workspace key (`<platform>/<workspace>`, every room in it) or a full surface key
6
+ // (`<platform>/<workspace>/<room>`, one room). Being in a memory domain never makes a surface
7
+ // internal: trust is decided here, and domains (agent/memory/config/domains/) only group rooms that
8
+ // are already trusted.
9
+ //
10
+ // Vouch your own Slack workspace first. Its id is the `T…` value in the workspace URL, or
11
+ // `team_id` in the Slack app settings:
12
+ //
13
+ // defineSurface({ key: "slack/<team-id>", trust: "internal" }),
14
+ //
15
+ // A Notion key names one page. Add `subtree: true` to vouch that page and every page under it:
16
+ //
17
+ // defineSurface({ key: "notion/<workspaceId>/<pageId>", trust: "internal", subtree: true }),
18
+ //
19
+ // A GitHub key names an owner, or one repository:
20
+ //
21
+ // defineSurface({ key: "github/your-org", trust: "internal" }),
22
+ //
23
+ // The full rules (Enterprise Grid, subtrees, public carve-outs) are in the header of
24
+ // `@open-everywhere/core/agent/identity/trust.ts` and in docs/security/ in the Open Everywhere
25
+ // repository.
26
+
27
+ import { defineSurface, registerSurfaces, type Surface } from "@open-everywhere/core/agent/identity/trust.js";
28
+ import {
29
+ registerDeploymentIdentity,
30
+ type DeploymentIdentity,
31
+ } from "@open-everywhere/core/agent/identity/deployment-identity.js";
32
+
33
+ // The deployment's own identity. `org` is the outermost partition key that every binding, usage row
34
+ // and audit row carries — change it and the deployment answers for a different tenant. `workspace`
35
+ // and `surface` only fill a delivery that carries no platform identity (an `eve dev` HTTP call).
36
+ export const deploymentIdentity: DeploymentIdentity = {
37
+ org: "default",
38
+ workspace: "dev",
39
+ surface: "default",
40
+ };
41
+
42
+ export const surfaces: readonly Surface[] = [
43
+ // defineSurface({ key: "slack/<team-id>", trust: "internal" }),
44
+ ];
45
+ void defineSurface;
46
+
47
+ registerSurfaces(surfaces);
48
+ registerDeploymentIdentity(deploymentIdentity);
@@ -0,0 +1,11 @@
1
+ // Dynamic per-scope instructions (identity, surface, ROLE prompt block, access summary,
2
+ // memory index, channel scope) — resolved at session/turn start by the core module.
3
+ //
4
+ // LOAD-BEARING re-export: Eve only discovers instruction sources under the APP's
5
+ // `agent/` root (`instructions.md` + this `instructions/` directory — the flat markdown
6
+ // is prepended before directory modules). This file went missing in the Stage-2
7
+ // monorepo split (01c3e43 moved the module into core without leaving a stub, unlike
8
+ // channels/hooks/tools), which silently unmounted ALL dynamic instructions — the
9
+ // compiled manifest carried `dynamicInstructions: []`. Restored with the roles work
10
+ // (item 26), whose per-turn `instructions` slot injects through this same resolver.
11
+ export { default } from "@open-everywhere/core/agent/instructions/access.js";