@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.1
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/AGENTSAM.md +55 -0
- package/README.md +12 -8
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- package/docs/CLI_SHELL.md +163 -53
- package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
- package/docs/RELEASES.md +16 -7
- package/docs/SOURCE_ARCHITECTURE.md +58 -0
- package/docs/TEST_TIERS.md +26 -0
- package/migrations/runtime/0001_cli_runtime.sql +298 -0
- package/package.json +45 -12
- package/packages/agentsam-repository/README.md +15 -0
- package/packages/agentsam-repository/package.json +25 -0
- package/packages/agentsam-repository/src/contracts.js +113 -0
- package/packages/agentsam-repository/src/index.js +3 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
- package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
- package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
- package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
- package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
- package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
- package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
- package/packages/connectors/cloudflare/package.json +10 -0
- package/packages/connectors/cloudflare/src/index.js +127 -0
- package/packages/connectors/cloudflare/src/owner.js +76 -0
- package/packages/connectors/cloudflare/src/routes.js +223 -0
- package/packages/connectors/cloudflare/src/vault.js +80 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/contracts/auth-config.js +18 -7
- package/packages/identity/tests/auth-config.test.mjs +9 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
- package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
- package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
- package/protocol/README.md +1 -0
- package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
- package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
- package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
- package/protocol/capabilities/manifest.json +47 -0
- package/protocol/context/context-budget.schema.json +10 -15
- package/protocol/context/context-item.schema.json +4 -5
- package/protocol/context/resolved-context-pack.schema.json +19 -14
- package/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -0
- package/protocol/repository/repository-contract.schema.json +24 -0
- package/protocol/repository/repository-dependency.schema.json +24 -0
- package/protocol/repository/repository-identity.schema.json +17 -0
- package/protocol/rpc/v1/common.proto +16 -0
- package/protocol/rpc/v1/errors.proto +35 -0
- package/protocol/rpc/v1/knowledge.proto +77 -0
- package/services/knowledge/package-lock.json +333 -0
- package/services/knowledge/package.json +5 -1
- package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
- package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
- package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
- package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
- package/skills/catalog.json +18 -0
- package/src/agent/capability-adapter.js +25 -13
- package/src/agent/index.js +1 -0
- package/src/agent/responses-runner.js +353 -0
- package/src/capabilities/repository-snapshot.js +3 -3
- package/src/cli.js +118 -31
- package/src/cloudflare/cpu-profile.js +115 -0
- package/src/cloudflare/index.js +14 -0
- package/src/cloudflare/wrangler.js +132 -0
- package/src/commands/account-auth.js +47 -0
- package/src/commands/cloudflare.js +58 -0
- package/src/commands/connections.js +93 -0
- package/src/commands/context-economics.js +129 -0
- package/src/commands/context.js +1 -1
- package/src/commands/db.js +20 -3
- package/src/commands/deploy.js +39 -3
- package/src/commands/env.js +90 -0
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +2 -5
- package/src/commands/knowledge.js +12 -4
- package/src/commands/merkle-persist.js +30 -11
- package/src/commands/merkle.js +1 -1
- package/src/commands/models.js +149 -46
- package/src/commands/ollama.js +26 -0
- package/src/commands/preferences.js +130 -61
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +5 -3
- package/src/commands/shell.js +568 -119
- package/src/commands/tunnel.js +2 -2
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +68 -6
- package/src/context/index.js +3 -1
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +44 -12
- package/src/errors/contract.js +236 -0
- package/src/errors/diagnostic.js +160 -0
- package/src/errors/index.js +23 -0
- package/src/eval/context.js +191 -0
- package/src/eval/index.js +1 -0
- package/src/index.js +68 -2
- package/src/knowledge/service/auth.js +13 -0
- package/src/knowledge/service/grpc-client.js +115 -0
- package/src/knowledge/service/grpc-codec.js +237 -0
- package/src/knowledge/service/grpc-server.js +83 -0
- package/src/knowledge/service/job-engine.js +248 -0
- package/src/knowledge/service/server.js +87 -135
- package/src/knowledge/source.js +1 -1
- package/src/lib/account-session.js +98 -0
- package/src/lib/agent-instructions.js +73 -0
- package/src/lib/auth.js +4 -0
- package/src/lib/cli-preferences.js +55 -24
- package/src/lib/deploy/git-guard.js +69 -0
- package/src/lib/deploy/health.js +57 -0
- package/src/lib/deploy/local-studio.js +283 -0
- package/src/lib/deploy/secret-scan.js +65 -0
- package/src/lib/deploy-receipt/index.js +2 -2
- package/src/lib/detect-context.js +2 -2
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/knowledge-docker.js +6 -3
- package/src/lib/local-sessions.js +148 -0
- package/src/lib/local-status.js +1 -1
- package/src/lib/project-config.js +1 -1
- package/src/lib/provider-credentials.js +183 -0
- package/src/lib/scaffold/templates/worker-api/index.js +101 -20
- package/src/lib/scaffold/wizards/worker-api.js +27 -11
- package/src/lib/slash-commands.js +23 -16
- package/src/local/migrations.js +93 -0
- package/src/local/runtime-store.js +141 -0
- package/src/local/sqlite.js +2 -0
- package/src/local-pty/server.js +113 -51
- package/src/models/catalog.js +135 -0
- package/src/models/discovery.js +292 -0
- package/src/models/index.js +7 -0
- package/src/providers/anthropic-messages.js +192 -0
- package/src/providers/cloudflare-chat.js +183 -0
- package/src/providers/factory.js +69 -0
- package/src/providers/gemini-generate-content.js +208 -0
- package/src/providers/index.js +10 -0
- package/src/providers/ollama-chat.js +148 -0
- package/src/providers/openai-responses.js +426 -0
- package/src/repository/index.js +14 -2
- package/src/rpc/generated/common_grpc_pb.js +1 -0
- package/src/rpc/generated/common_pb.js +536 -0
- package/src/rpc/generated/errors_grpc_pb.js +1 -0
- package/src/rpc/generated/errors_pb.js +482 -0
- package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
- package/src/rpc/generated/knowledge_pb.js +2168 -0
- package/src/rpc/generated/package.json +3 -0
- package/src/security/process.js +35 -9
- package/src/security/trust-boundary.js +2 -2
- package/src/telemetry/contracts.js +203 -0
- package/src/telemetry/events.js +51 -0
- package/src/telemetry/index.js +8 -0
- package/src/tools/hydrate.js +35 -0
- package/src/tools/index.js +1 -0
- package/src/ui/boot.js +15 -17
- package/src/ui/cli/activity.js +76 -0
- package/src/ui/cli/compaction.js +15 -0
- package/src/ui/cli/footer.js +39 -0
- package/src/ui/cli/help.js +192 -0
- package/src/ui/cli/plan.js +20 -0
- package/src/ui/cli/runtime-events.js +110 -0
- package/src/ui/cli/waiting.js +16 -0
- package/src/ui/merkle/render.js +1 -1
- package/test/account-session.test.mjs +36 -0
- package/test/cli/preferences-runtime.test.mjs +11 -0
- package/test/cli/runtime-ui.test.mjs +74 -0
- package/test/cli-preferences.test.mjs +26 -5
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +61 -12
- package/test/deploy-health-scan.test.mjs +67 -0
- package/test/error-diagnostics.test.mjs +115 -0
- package/test/eval-context.test.mjs +37 -0
- package/test/execution-approvals.test.mjs +27 -0
- package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
- package/test/integration/cli-help.test.mjs +37 -0
- package/test/integration/knowledge-rpc.test.mjs +112 -0
- package/test/integration/merkle-cli.test.mjs +61 -0
- package/test/integration/merkle-persistence-identity.test.mjs +48 -0
- package/test/integration/provider-env-cli.test.mjs +49 -0
- package/test/integration/provider-factory.test.mjs +197 -0
- package/test/integration/repository-company-graph.test.mjs +90 -0
- package/test/integration/runtime-migrations.test.mjs +82 -0
- package/test/knowledge-service.test.mjs +5 -0
- package/test/knowledge.test.mjs +16 -0
- package/test/live/terminal-transport.live.test.mjs +24 -0
- package/test/local-sessions.test.mjs +48 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +127 -16
- package/test/npm10-lock.test.mjs +29 -0
- package/test/ollama.test.mjs +21 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/portable-context.test.mjs +1 -1
- package/test/provider-credentials.test.mjs +96 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +13 -5
- package/test/responses-runner.test.mjs +150 -0
- package/test/shell.test.mjs +92 -23
- package/test/smoke.mjs +4 -1
- package/test/telemetry.test.mjs +79 -0
- package/test/terminal/local-pty.mock.test.mjs +151 -0
- package/test/tools-search.test.mjs +14 -1
- package/test/whoami-resume.test.mjs +56 -0
- /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLOUDFLARE_CALLBACK_PATH,
|
|
3
|
+
CLOUDFLARE_OAUTH_TOKEN_URL,
|
|
4
|
+
CLOUDFLARE_OAUTH_REVOKE_URL,
|
|
5
|
+
buildAuthorizeUrl,
|
|
6
|
+
cloudflareConnectionSafeStatus,
|
|
7
|
+
resolveCloudflareOAuthClient,
|
|
8
|
+
requestedCloudflareScopes,
|
|
9
|
+
assertConnectionOwner,
|
|
10
|
+
} from './index.js';
|
|
11
|
+
import { resolveAuthenticatedOwner } from './owner.js';
|
|
12
|
+
import { sealToken, vaultConfigured } from './vault.js';
|
|
13
|
+
|
|
14
|
+
function json(data, status = 200) {
|
|
15
|
+
return new Response(JSON.stringify(data), {
|
|
16
|
+
status,
|
|
17
|
+
headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function redirect(location) {
|
|
22
|
+
return new Response(null, { status: 302, headers: { location } });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isCloudflareConnectionPath(pathname) {
|
|
26
|
+
return pathname === '/api/connections/cloudflare'
|
|
27
|
+
|| pathname === '/api/connections/cloudflare/start'
|
|
28
|
+
|| pathname === CLOUDFLARE_CALLBACK_PATH
|
|
29
|
+
|| pathname === '/api/connections/cloudflare/disconnect';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function ensureTables(env) {
|
|
33
|
+
if (!env?.DB?.prepare) return;
|
|
34
|
+
await env.DB.prepare(`CREATE TABLE IF NOT EXISTS agentsam_cloudflare_connections (
|
|
35
|
+
connection_id TEXT PRIMARY KEY,
|
|
36
|
+
owner_id TEXT NOT NULL,
|
|
37
|
+
cloudflare_account_id TEXT,
|
|
38
|
+
scopes TEXT,
|
|
39
|
+
status TEXT,
|
|
40
|
+
access_token_encrypted TEXT,
|
|
41
|
+
refresh_token_encrypted TEXT,
|
|
42
|
+
created_at INTEGER,
|
|
43
|
+
updated_at INTEGER,
|
|
44
|
+
expires_at INTEGER
|
|
45
|
+
)`).run();
|
|
46
|
+
await env.DB.prepare(`CREATE TABLE IF NOT EXISTS agentsam_cloudflare_oauth_state (
|
|
47
|
+
state TEXT PRIMARY KEY,
|
|
48
|
+
owner_id TEXT NOT NULL,
|
|
49
|
+
code_verifier TEXT NOT NULL,
|
|
50
|
+
created_at INTEGER
|
|
51
|
+
)`).run();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function handleCloudflareConnectionRequest(request, env) {
|
|
55
|
+
const url = new URL(request.url);
|
|
56
|
+
const client = resolveCloudflareOAuthClient(env);
|
|
57
|
+
if (request.method === 'OPTIONS') {
|
|
58
|
+
return new Response(null, { status: 204 });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let body = {};
|
|
62
|
+
if (request.method === 'POST') {
|
|
63
|
+
body = await request.json().catch(() => ({}));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const isCallback = url.pathname === CLOUDFLARE_CALLBACK_PATH && request.method === 'GET';
|
|
67
|
+
let ownerId = '';
|
|
68
|
+
try {
|
|
69
|
+
ownerId = await resolveAuthenticatedOwner(request, env, url, body);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (!isCallback || err.code === 'untrusted_owner_hint') {
|
|
72
|
+
const code = err.code || 'unauthenticated';
|
|
73
|
+
return json({ ok: false, error: code }, code === 'untrusted_owner_hint' ? 400 : 401);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!client.productionReady) {
|
|
78
|
+
if (url.pathname === '/api/connections/cloudflare' && request.method === 'GET') {
|
|
79
|
+
return json({
|
|
80
|
+
ok: true,
|
|
81
|
+
...cloudflareConnectionSafeStatus(env, null, ownerId),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (url.pathname === '/api/connections/cloudflare/start') {
|
|
85
|
+
return json({
|
|
86
|
+
ok: false,
|
|
87
|
+
error: 'not_configured',
|
|
88
|
+
message: 'real Cloudflare OAuth client still required',
|
|
89
|
+
fixture: client.fixture,
|
|
90
|
+
}, 503);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await ensureTables(env);
|
|
95
|
+
|
|
96
|
+
if (url.pathname === '/api/connections/cloudflare' && request.method === 'GET') {
|
|
97
|
+
let connection = null;
|
|
98
|
+
if (env.DB) {
|
|
99
|
+
const row = await env.DB.prepare(
|
|
100
|
+
`SELECT connection_id, owner_id, cloudflare_account_id, scopes, status, created_at, updated_at, expires_at
|
|
101
|
+
FROM agentsam_cloudflare_connections WHERE owner_id = ? LIMIT 1`,
|
|
102
|
+
).bind(ownerId).first();
|
|
103
|
+
if (row) {
|
|
104
|
+
connection = {
|
|
105
|
+
connectionId: row.connection_id,
|
|
106
|
+
ownerId: row.owner_id,
|
|
107
|
+
cloudflareAccountId: row.cloudflare_account_id,
|
|
108
|
+
scopes: row.scopes ? String(row.scopes).split(' ') : [],
|
|
109
|
+
status: row.status,
|
|
110
|
+
createdAt: row.created_at,
|
|
111
|
+
updatedAt: row.updated_at,
|
|
112
|
+
expiresAt: row.expires_at,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return json({ ok: true, ...cloudflareConnectionSafeStatus(env, connection, ownerId) });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (url.pathname === '/api/connections/cloudflare/start' && request.method === 'GET') {
|
|
120
|
+
const state = crypto.randomUUID().replace(/-/g, '');
|
|
121
|
+
const verifierBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
122
|
+
const verifier = btoa(String.fromCharCode(...verifierBytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
123
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
|
|
124
|
+
const challenge = btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
125
|
+
if (env.DB) {
|
|
126
|
+
await env.DB.prepare(
|
|
127
|
+
`INSERT INTO agentsam_cloudflare_oauth_state (state, owner_id, code_verifier, created_at) VALUES (?, ?, ?, unixepoch())`,
|
|
128
|
+
).bind(state, ownerId, verifier).run();
|
|
129
|
+
} else if (env.oauthState instanceof Map) {
|
|
130
|
+
env.oauthState.set(state, { ownerId, verifier });
|
|
131
|
+
}
|
|
132
|
+
const redirectUri = `${url.origin}${CLOUDFLARE_CALLBACK_PATH}`;
|
|
133
|
+
const authorize = buildAuthorizeUrl({
|
|
134
|
+
clientId: String(env.CLOUDFLARE_OAUTH_CLIENT_ID),
|
|
135
|
+
redirectUri,
|
|
136
|
+
state,
|
|
137
|
+
codeChallenge: challenge,
|
|
138
|
+
});
|
|
139
|
+
return json({ ok: true, authorize_url: authorize, callback: CLOUDFLARE_CALLBACK_PATH });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (url.pathname === CLOUDFLARE_CALLBACK_PATH && request.method === 'GET') {
|
|
143
|
+
const code = url.searchParams.get('code') || '';
|
|
144
|
+
const state = url.searchParams.get('state') || '';
|
|
145
|
+
let stored = null;
|
|
146
|
+
if (env.DB) {
|
|
147
|
+
stored = await env.DB.prepare(
|
|
148
|
+
`SELECT owner_id, code_verifier FROM agentsam_cloudflare_oauth_state WHERE state = ?`,
|
|
149
|
+
).bind(state).first();
|
|
150
|
+
} else if (env.oauthState instanceof Map) {
|
|
151
|
+
stored = env.oauthState.get(state);
|
|
152
|
+
if (stored) stored = { owner_id: stored.ownerId, code_verifier: stored.verifier };
|
|
153
|
+
}
|
|
154
|
+
if (!stored) {
|
|
155
|
+
return json({ ok: false, error: 'cloudflare_connection_forbidden' }, 403);
|
|
156
|
+
}
|
|
157
|
+
if (!ownerId) ownerId = stored.owner_id;
|
|
158
|
+
if (stored.owner_id !== ownerId) {
|
|
159
|
+
return json({ ok: false, error: 'cloudflare_connection_forbidden' }, 403);
|
|
160
|
+
}
|
|
161
|
+
const tokenRes = await fetch(CLOUDFLARE_OAUTH_TOKEN_URL, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
164
|
+
body: new URLSearchParams({
|
|
165
|
+
grant_type: 'authorization_code',
|
|
166
|
+
code,
|
|
167
|
+
redirect_uri: `${url.origin}${CLOUDFLARE_CALLBACK_PATH}`,
|
|
168
|
+
client_id: String(env.CLOUDFLARE_OAUTH_CLIENT_ID),
|
|
169
|
+
client_secret: String(env.CLOUDFLARE_OAUTH_CLIENT_SECRET),
|
|
170
|
+
code_verifier: stored.code_verifier,
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
if (!tokenRes.ok) {
|
|
174
|
+
return json({ ok: false, error: 'token_exchange_failed' }, 502);
|
|
175
|
+
}
|
|
176
|
+
const tokens = await tokenRes.json();
|
|
177
|
+
const connectionId = `cfconn_${crypto.randomUUID().replace(/-/g, '').slice(0, 16)}`;
|
|
178
|
+
if (tokens.access_token && !vaultConfigured(env)) {
|
|
179
|
+
return json({ ok: false, error: 'vault_unavailable' }, 503);
|
|
180
|
+
}
|
|
181
|
+
const aad = `cloudflare-connection:${ownerId}`;
|
|
182
|
+
let accessEnc = null;
|
|
183
|
+
let refreshEnc = null;
|
|
184
|
+
try {
|
|
185
|
+
accessEnc = await sealToken(env, tokens.access_token, aad);
|
|
186
|
+
refreshEnc = await sealToken(env, tokens.refresh_token, aad);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
return json({ ok: false, error: err.code || 'vault_unavailable' }, 503);
|
|
189
|
+
}
|
|
190
|
+
if (env.DB) {
|
|
191
|
+
await env.DB.prepare(
|
|
192
|
+
`INSERT INTO agentsam_cloudflare_connections (
|
|
193
|
+
connection_id, owner_id, cloudflare_account_id, scopes, status,
|
|
194
|
+
access_token_encrypted, refresh_token_encrypted, created_at, updated_at, expires_at
|
|
195
|
+
) VALUES (?, ?, ?, ?, 'connected', ?, ?, unixepoch(), unixepoch(), ?)`,
|
|
196
|
+
).bind(
|
|
197
|
+
connectionId,
|
|
198
|
+
ownerId,
|
|
199
|
+
tokens.account_id || null,
|
|
200
|
+
(tokens.scope || requestedCloudflareScopes().join(' ')),
|
|
201
|
+
accessEnc,
|
|
202
|
+
refreshEnc,
|
|
203
|
+
tokens.expires_in ? Math.floor(Date.now() / 1000) + Number(tokens.expires_in) : null,
|
|
204
|
+
).run();
|
|
205
|
+
}
|
|
206
|
+
return redirect(`/agentsam?connection=cloudflare`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (url.pathname === '/api/connections/cloudflare/disconnect' && request.method === 'POST') {
|
|
210
|
+
if (env.DB) {
|
|
211
|
+
const row = await env.DB.prepare(
|
|
212
|
+
`SELECT connection_id, owner_id FROM agentsam_cloudflare_connections WHERE owner_id = ?`,
|
|
213
|
+
).bind(ownerId).first();
|
|
214
|
+
if (row) {
|
|
215
|
+
assertConnectionOwner({ ownerId: row.owner_id, connectionId: row.connection_id }, ownerId);
|
|
216
|
+
await env.DB.prepare(`DELETE FROM agentsam_cloudflare_connections WHERE owner_id = ?`).bind(ownerId).run();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return json({ ok: true, status: 'not_configured' });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return json({ ok: false, error: 'not_found' }, 404);
|
|
223
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-256-GCM token vault matching apps/local-studio/backend/worker encryptSecret.
|
|
3
|
+
* Connector tokens are server-only. Never return plaintext on status routes.
|
|
4
|
+
*/
|
|
5
|
+
const enc = new TextEncoder();
|
|
6
|
+
const dec = new TextDecoder();
|
|
7
|
+
|
|
8
|
+
function b64ToBytes(value) {
|
|
9
|
+
const bin = atob(value);
|
|
10
|
+
const out = new Uint8Array(bin.length);
|
|
11
|
+
for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function bytesToB64(bytes) {
|
|
16
|
+
let bin = '';
|
|
17
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
18
|
+
return btoa(bin);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function vaultConfigured(env = {}) {
|
|
22
|
+
return Boolean(env.VAULT_MASTER_KEY || env.VAULT_KEY);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function importVaultKey(env) {
|
|
26
|
+
const raw = env.VAULT_MASTER_KEY || env.VAULT_KEY;
|
|
27
|
+
if (!raw) throw new Error('VAULT_MASTER_KEY missing');
|
|
28
|
+
let keyBytes;
|
|
29
|
+
try {
|
|
30
|
+
keyBytes = b64ToBytes(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
keyBytes = enc.encode(raw);
|
|
33
|
+
}
|
|
34
|
+
if (keyBytes.byteLength === 32) {
|
|
35
|
+
// ok
|
|
36
|
+
} else if (keyBytes.byteLength > 32) {
|
|
37
|
+
keyBytes = keyBytes.slice(0, 32);
|
|
38
|
+
} else {
|
|
39
|
+
const hash = await crypto.subtle.digest('SHA-256', keyBytes);
|
|
40
|
+
keyBytes = new Uint8Array(hash);
|
|
41
|
+
}
|
|
42
|
+
return crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function encryptSecret(env, plaintext, aad) {
|
|
46
|
+
const key = await importVaultKey(env);
|
|
47
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
48
|
+
const cipher = await crypto.subtle.encrypt(
|
|
49
|
+
{ name: 'AES-GCM', iv, additionalData: enc.encode(aad || '') },
|
|
50
|
+
key,
|
|
51
|
+
enc.encode(plaintext),
|
|
52
|
+
);
|
|
53
|
+
const packed = new Uint8Array(iv.byteLength + cipher.byteLength);
|
|
54
|
+
packed.set(iv, 0);
|
|
55
|
+
packed.set(new Uint8Array(cipher), iv.byteLength);
|
|
56
|
+
return bytesToB64(packed);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function decryptSecret(env, packedB64, aad) {
|
|
60
|
+
const key = await importVaultKey(env);
|
|
61
|
+
const packed = b64ToBytes(packedB64);
|
|
62
|
+
const iv = packed.slice(0, 12);
|
|
63
|
+
const data = packed.slice(12);
|
|
64
|
+
const plain = await crypto.subtle.decrypt(
|
|
65
|
+
{ name: 'AES-GCM', iv, additionalData: enc.encode(aad || '') },
|
|
66
|
+
key,
|
|
67
|
+
data,
|
|
68
|
+
);
|
|
69
|
+
return dec.decode(plain);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function sealToken(env, plaintext, aad) {
|
|
73
|
+
if (!plaintext) return null;
|
|
74
|
+
if (!vaultConfigured(env)) {
|
|
75
|
+
const err = new Error('vault_unavailable');
|
|
76
|
+
err.code = 'vault_unavailable';
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
return encryptSecret(env, plaintext, aad);
|
|
80
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import {
|
|
4
|
+
assertConnectionOwner,
|
|
5
|
+
cloudflareConnectionSafeStatus,
|
|
6
|
+
resolveCloudflareOAuthClient,
|
|
7
|
+
} from '../src/index.js';
|
|
8
|
+
|
|
9
|
+
describe('cloudflare connector', () => {
|
|
10
|
+
it('reports not_configured when credentials are absent', () => {
|
|
11
|
+
const client = resolveCloudflareOAuthClient({});
|
|
12
|
+
assert.equal(client.status, 'not_configured');
|
|
13
|
+
assert.equal(client.configured, false);
|
|
14
|
+
assert.equal(client.productionReady, false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('loads fake fixtures without treating them as production authority', () => {
|
|
18
|
+
const client = resolveCloudflareOAuthClient({
|
|
19
|
+
CLOUDFLARE_OAUTH_CLIENT_ID: 'sillynotreal',
|
|
20
|
+
CLOUDFLARE_OAUTH_CLIENT_SECRET: 'sillynotreal-secret',
|
|
21
|
+
});
|
|
22
|
+
assert.equal(client.configured, true);
|
|
23
|
+
assert.equal(client.fixture, true);
|
|
24
|
+
assert.equal(client.productionReady, false);
|
|
25
|
+
assert.equal(client.status, 'fixture');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('never includes secrets in safe status output', () => {
|
|
29
|
+
const status = cloudflareConnectionSafeStatus({
|
|
30
|
+
CLOUDFLARE_OAUTH_CLIENT_ID: 'sillynotreal',
|
|
31
|
+
CLOUDFLARE_OAUTH_CLIENT_SECRET: 'sillynotreal-secret',
|
|
32
|
+
});
|
|
33
|
+
const blob = JSON.stringify(status);
|
|
34
|
+
assert.equal(blob.includes('sillynotreal-secret'), false);
|
|
35
|
+
assert.equal(status.secret, 'configured');
|
|
36
|
+
assert.equal(status.configured, false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('denies cross-account connection access', () => {
|
|
40
|
+
const record = { ownerId: 'sam', connectionId: 'c1' };
|
|
41
|
+
assert.throws(() => assertConnectionOwner(record, 'connor'), /cloudflare_connection_forbidden/);
|
|
42
|
+
assert.equal(assertConnectionOwner(record, 'sam').connectionId, 'c1');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inneranimalmedia/agentsam-sdk-identity",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "Identity module for @inneranimalmedia/agentsam-sdk (workspace
|
|
6
|
+
"description": "Identity module for @inneranimalmedia/agentsam-sdk (workspace — publish via root SDK)",
|
|
7
7
|
"agentsam": {
|
|
8
8
|
"system": "identity",
|
|
9
9
|
"kind": "library",
|
|
@@ -5,20 +5,24 @@
|
|
|
5
5
|
* may provide storage/verification adapters without inventing competing aliases.
|
|
6
6
|
*
|
|
7
7
|
* Canonical variables:
|
|
8
|
-
*
|
|
8
|
+
* IAM_OAUTH_ISSUER public authorization-server authority (issuer)
|
|
9
9
|
* IAM_CLIENT_ID OAuth client id
|
|
10
10
|
* IAM_CLIENT_SECRET OAuth client secret
|
|
11
|
+
* IAM_ORIGIN deprecated alias of IAM_OAUTH_ISSUER (migration window)
|
|
11
12
|
* AGENTSAM_SDK_KEY account/delegated sdk_* bearer
|
|
12
13
|
* AGENTSAM_BRIDGE_KEY machine/integration credential
|
|
13
14
|
*
|
|
14
15
|
* Migration-only fallbacks:
|
|
15
|
-
*
|
|
16
|
+
* IAM_ORIGIN -> IAM_OAUTH_ISSUER
|
|
16
17
|
* AGENTSAM_SDK_TOKEN -> AGENTSAM_SDK_KEY
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
|
-
export const
|
|
20
|
+
export const DEFAULT_IAM_OAUTH_ISSUER = 'https://inneranimalmedia.com';
|
|
21
|
+
/** @deprecated Use DEFAULT_IAM_OAUTH_ISSUER. */
|
|
22
|
+
export const DEFAULT_IAM_ORIGIN = DEFAULT_IAM_OAUTH_ISSUER;
|
|
20
23
|
|
|
21
24
|
export const AGENTSAM_AUTH_ENV = Object.freeze({
|
|
25
|
+
iamIssuer: 'IAM_OAUTH_ISSUER',
|
|
22
26
|
iamOrigin: 'IAM_ORIGIN',
|
|
23
27
|
iamClientId: 'IAM_CLIENT_ID',
|
|
24
28
|
iamClientSecret: 'IAM_CLIENT_SECRET',
|
|
@@ -27,7 +31,7 @@ export const AGENTSAM_AUTH_ENV = Object.freeze({
|
|
|
27
31
|
});
|
|
28
32
|
|
|
29
33
|
export const AGENTSAM_AUTH_LEGACY_ENV = Object.freeze({
|
|
30
|
-
iamOrigin: '
|
|
34
|
+
iamOrigin: 'IAM_ORIGIN',
|
|
31
35
|
sdkKey: 'AGENTSAM_SDK_TOKEN',
|
|
32
36
|
});
|
|
33
37
|
|
|
@@ -35,6 +39,7 @@ export const AGENTSAM_AUTH_CONTRACT = Object.freeze({
|
|
|
35
39
|
version: 1,
|
|
36
40
|
authority: 'agentsam-sdk',
|
|
37
41
|
iam: Object.freeze({
|
|
42
|
+
issuer: AGENTSAM_AUTH_ENV.iamIssuer,
|
|
38
43
|
origin: AGENTSAM_AUTH_ENV.iamOrigin,
|
|
39
44
|
clientId: AGENTSAM_AUTH_ENV.iamClientId,
|
|
40
45
|
clientSecret: AGENTSAM_AUTH_ENV.iamClientSecret,
|
|
@@ -54,6 +59,7 @@ export const AGENTSAM_AUTH_CONTRACT = Object.freeze({
|
|
|
54
59
|
}),
|
|
55
60
|
compatibility: Object.freeze({
|
|
56
61
|
iamOriginFallback: AGENTSAM_AUTH_LEGACY_ENV.iamOrigin,
|
|
62
|
+
iamIssuerCanonical: AGENTSAM_AUTH_ENV.iamIssuer,
|
|
57
63
|
sdkKeyFallback: AGENTSAM_AUTH_LEGACY_ENV.sdkKey,
|
|
58
64
|
}),
|
|
59
65
|
});
|
|
@@ -66,13 +72,18 @@ function normalizeOrigin(value) {
|
|
|
66
72
|
return clean(value).replace(/\/+$/, '');
|
|
67
73
|
}
|
|
68
74
|
|
|
69
|
-
/** Resolve canonical IAM
|
|
70
|
-
export function
|
|
75
|
+
/** Resolve canonical IAM issuer. IAM_OAUTH_ISSUER wins; IAM_ORIGIN is compatibility only. */
|
|
76
|
+
export function resolveIamIssuer(env = {}, explicit = '') {
|
|
71
77
|
return normalizeOrigin(
|
|
72
|
-
explicit || env?.
|
|
78
|
+
explicit || env?.IAM_OAUTH_ISSUER || env?.IAM_ORIGIN || DEFAULT_IAM_OAUTH_ISSUER,
|
|
73
79
|
);
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
/** @deprecated Use resolveIamIssuer. Same resolution order as issuer. */
|
|
83
|
+
export function resolveIamOrigin(env = {}, explicit = '') {
|
|
84
|
+
return resolveIamIssuer(env, explicit);
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
/** Resolve account/delegated SDK bearer; canonical name wins over legacy. */
|
|
77
88
|
export function resolveSdkKey(env = {}, explicit = '') {
|
|
78
89
|
return clean(explicit || env?.AGENTSAM_SDK_KEY || env?.AGENTSAM_SDK_TOKEN);
|
|
@@ -9,23 +9,27 @@ import {
|
|
|
9
9
|
} from '../src/contracts/auth-config.js';
|
|
10
10
|
|
|
11
11
|
describe('auth configuration contract', () => {
|
|
12
|
-
it('prefers canonical
|
|
12
|
+
it('prefers canonical IAM_OAUTH_ISSUER over IAM_ORIGIN', () => {
|
|
13
13
|
assert.equal(
|
|
14
14
|
resolveIamOrigin({
|
|
15
|
-
IAM_ORIGIN: 'https://
|
|
16
|
-
IAM_OAUTH_ISSUER: 'https://
|
|
15
|
+
IAM_ORIGIN: 'https://legacy.example.test/',
|
|
16
|
+
IAM_OAUTH_ISSUER: 'https://canonical.example.test/',
|
|
17
17
|
}),
|
|
18
18
|
'https://canonical.example.test',
|
|
19
19
|
);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
it('accepts
|
|
22
|
+
it('accepts IAM_ORIGIN as migration-only fallback', () => {
|
|
23
23
|
assert.equal(
|
|
24
|
-
resolveIamOrigin({
|
|
24
|
+
resolveIamOrigin({ IAM_ORIGIN: 'https://legacy.example.test/' }),
|
|
25
25
|
'https://legacy.example.test',
|
|
26
26
|
);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
it('defaults issuer when neither IAM_OAUTH_ISSUER nor IAM_ORIGIN exists', () => {
|
|
30
|
+
assert.equal(resolveIamOrigin({}), 'https://inneranimalmedia.com');
|
|
31
|
+
});
|
|
32
|
+
|
|
29
33
|
it('prefers AGENTSAM_SDK_KEY over AGENTSAM_SDK_TOKEN', () => {
|
|
30
34
|
assert.equal(
|
|
31
35
|
resolveSdkKey({
|
|
@@ -46,18 +46,18 @@ describe('oauth credentials', () => {
|
|
|
46
46
|
assert.equal(resolveOAuthCredentialLane(env, 'iam')?.lane, 'iam_platform');
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
it('prefers
|
|
49
|
+
it('prefers IAM_OAUTH_ISSUER and exposes issuer as the canonical authority', () => {
|
|
50
50
|
const creds = resolveIamPlatformCredentials({
|
|
51
51
|
IAM_CLIENT_ID: 'c',
|
|
52
52
|
IAM_CLIENT_SECRET: 's',
|
|
53
|
-
IAM_ORIGIN: 'https://
|
|
54
|
-
IAM_OAUTH_ISSUER: 'https://
|
|
53
|
+
IAM_ORIGIN: 'https://legacy.example.test/',
|
|
54
|
+
IAM_OAUTH_ISSUER: 'https://iam.example.test/',
|
|
55
55
|
});
|
|
56
56
|
assert.equal(creds?.origin, 'https://iam.example.test');
|
|
57
57
|
assert.equal(creds?.issuer, 'https://iam.example.test');
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it('accepts
|
|
60
|
+
it('accepts IAM_ORIGIN only as a migration fallback', () => {
|
|
61
61
|
const creds = resolveIamPlatformCredentials({
|
|
62
62
|
IAM_CLIENT_ID: 'c',
|
|
63
63
|
IAM_CLIENT_SECRET: 's',
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# AgentSam Company Repository Graph v1
|
|
2
|
+
|
|
3
|
+
This protocol defines the portable vocabulary for company-wide repository identity, public contracts, dependency edges, and Merkle evidence. It does not make the SDK a global database.
|
|
4
|
+
|
|
5
|
+
## Authority split
|
|
6
|
+
|
|
7
|
+
- `agentsam-sdk` defines schemas, enums, builders, Merkle formats, and receipt semantics.
|
|
8
|
+
- The authenticated host owns `account_id` and persists the operational graph.
|
|
9
|
+
- `code_repositories` is the canonical repository registry.
|
|
10
|
+
- `agentsam_repository_contracts` records what a repository provides.
|
|
11
|
+
- `agentsam_repository_dependencies` records what a repository consumes and the policy attached to that edge.
|
|
12
|
+
- `agentsam_fs_merkle_snapshots` indexes exact repository states; full manifests may live in object storage.
|
|
13
|
+
- Deployment and deployment-health tables record whether a source state reached production and whether it remains healthy.
|
|
14
|
+
- MCP may expose graph queries; ExecOS may emit execution evidence. Neither becomes the ownership authority.
|
|
15
|
+
|
|
16
|
+
## Ownership law
|
|
17
|
+
|
|
18
|
+
Persisted company graph records use `account_id` as ownership SSOT. `tenant_id`, `workspace_id`, `user_id`, and `owner_user_id` are not part of this protocol.
|
|
19
|
+
|
|
20
|
+
Portable repository identity is deliberately different: a checked-in repository identity must not embed an account identifier. The host binds its stable `repository_id` to an authenticated `account_id` in the registry.
|
|
21
|
+
|
|
22
|
+
## Canonical repository identity
|
|
23
|
+
|
|
24
|
+
A Git remote is evidence, not authority. The SDK must not derive the canonical `repository_id` from `owner/repo`, a checkout path, a workspace, or a provider URL. Persistence callers provide the `repository_id` from the host registry.
|
|
25
|
+
|
|
26
|
+
## Graph levels
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
code_repositories
|
|
30
|
+
repository identity / company role
|
|
31
|
+
|
|
|
32
|
+
+-- agentsam_repository_contracts
|
|
33
|
+
| what this repository provides
|
|
34
|
+
|
|
|
35
|
+
+-- agentsam_repository_dependencies
|
|
36
|
+
what this repository consumes
|
|
37
|
+
|
|
|
38
|
+
+-- Merkle / AST / Git evidence
|
|
39
|
+
|
|
|
40
|
+
+-- deployments / deployment health
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`codebase_dep_edges` remains an intra-repository code graph. `agentsam_repository_dependencies` is a cross-repository company graph. They are not interchangeable.
|
|
44
|
+
|
|
45
|
+
## Contract records
|
|
46
|
+
|
|
47
|
+
Contract types are:
|
|
48
|
+
|
|
49
|
+
`api | schema | runtime | cli | event | receipt | package`
|
|
50
|
+
|
|
51
|
+
Statuses are:
|
|
52
|
+
|
|
53
|
+
`active | deprecated | retired`
|
|
54
|
+
|
|
55
|
+
A contract record carries a stable `contract_key`, explicit version, manifest path when one exists, and `contract_hash`. The hash is the semantic identity consumers can pin independently of a Git branch name.
|
|
56
|
+
|
|
57
|
+
## Dependency records
|
|
58
|
+
|
|
59
|
+
Dependency types are:
|
|
60
|
+
|
|
61
|
+
`runtime | build | contract | package | deploy | tool | data`
|
|
62
|
+
|
|
63
|
+
Criticality is:
|
|
64
|
+
|
|
65
|
+
`informational | compatible | strict | critical`
|
|
66
|
+
|
|
67
|
+
Failure policy is:
|
|
68
|
+
|
|
69
|
+
`warn | block_certification | block_deploy | degrade`
|
|
70
|
+
|
|
71
|
+
The dependency graph supplies the meaning a Merkle tree cannot infer by itself: who consumes a changed contract and what should happen if the requirement is violated.
|
|
72
|
+
|
|
73
|
+
## Merkle persistence v2
|
|
74
|
+
|
|
75
|
+
Company persistence uses:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
account_id + repository_id + snapshot_id
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Object keys are:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
agentsam_fs_merkle_snapshots/<account_id>/<repository_id>/<snapshot_id>.json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The content root, metadata root, and policy hash remain separate authorities. A future contract/dependency aggregate root may be layered above these records without changing the content Merkle format.
|
|
88
|
+
|
|
89
|
+
## Certification
|
|
90
|
+
|
|
91
|
+
Version 1 intentionally does not require a separate certifications table. A host can derive an initial certification decision from repository identity, contract hashes, dependency requirements, Merkle roots, verification results, deployments, and deployment health. A persistent certification ledger can be added when there is a real need to preserve compatibility statements across an exact set of repository roots.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# AgentSam Merkle Persistence v1
|
|
2
2
|
|
|
3
|
+
> Superseded for current company persistence by `MERKLE_PERSISTENCE_V2.md`. V1 is retained only as the historical contract shipped before the account/repository hard cut.
|
|
4
|
+
|
|
3
5
|
Persistence is a host concern layered on top of the portable Merkle protocol. The SDK exposes the canonical table name, storage prefix, logical asset binding, storage-key helper, and portable SQL schema without owning a customer's database or cloud credentials.
|
|
4
6
|
|
|
5
7
|
## Authority split
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# AgentSam Merkle Persistence v2
|
|
2
|
+
|
|
3
|
+
Persistence is a host concern layered on top of the portable Merkle protocol. Version 2 hard-cuts persisted ownership to `account_id + repository_id` and removes user/workspace/tenant ownership aliases.
|
|
4
|
+
|
|
5
|
+
## Authority split
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
root_hash = filesystem/content authority
|
|
9
|
+
metadata_root = semantic/index authority
|
|
10
|
+
policy_hash = capture-scope authority
|
|
11
|
+
account_id = authenticated ownership authority
|
|
12
|
+
repository_id = canonical repository-registry identity
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
A checkout path, workspace, or user identity must never become `repository_id`. For registered Git repositories, the SDK may derive the canonical provider identity directly from the Git remote (for example `github:owner/repo`), matching the `code_repositories.id` convention; local repositories may fall back to the committed project manifest.
|
|
16
|
+
|
|
17
|
+
The optional searchable index table remains `agentsam_fs_merkle_snapshots`. Full manifests may be stored under the provider-neutral object prefix `agentsam_fs_merkle_snapshots/`.
|
|
18
|
+
|
|
19
|
+
Typical keys are:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
agentsam_fs_merkle_snapshots/<account_id>/<repository_id>/<snapshot_id>.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The hosting application chooses physical storage and database bindings. The SDK never owns cloud credentials and never invents account or repository authority.
|
|
26
|
+
|
|
27
|
+
## CLI publication
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
agentsam login
|
|
31
|
+
agentsam merkle snapshot . --semantic --out .agentsam/merkle.json
|
|
32
|
+
agentsam merkle persist .agentsam/merkle.json \
|
|
33
|
+
--wrangler-config path/to/wrangler.toml \
|
|
34
|
+
--capture-kind deploy \
|
|
35
|
+
--deployment-id "$DEPLOYMENT_ID"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The CLI reads `account_id` from the authenticated local AgentSam session and resolves `repository_id` from Git/provider identity, with the committed project manifest as fallback for local repositories. Programmatic hosts still pass authenticated `account_id + repository_id` directly to `buildMerklePersistencePlan`. For non-deploy captures, execution provenance remains required through a connection or runtime lease. `--dry-run` emits the exact storage/index plan without writes; `--r2-only` uploads the manifest without a D1 index write.
|
|
39
|
+
|
|
40
|
+
Version 1 used `owner_user_id` / `repo_id` and is superseded for current company persistence.
|
package/protocol/README.md
CHANGED
|
@@ -12,6 +12,7 @@ Platform repositories may consume the SDK, provide host adapters, or incubate pr
|
|
|
12
12
|
4. **Contracts travel with the SDK.** Shared schemas and protocol definitions live under `protocol/` and are versioned with the code that consumes them.
|
|
13
13
|
5. **No copied package trees.** Do not duplicate `protocol/`, `agentsam_sdk/`, or package source under another path in this repository or another repository merely to keep two homes in sync.
|
|
14
14
|
6. **Repository tools observe before they prescribe.** Generic repository intelligence must accept an explicit repo root, be read-only by default, and must not hardcode one repository's folder names, tenant IDs, workspace IDs, providers, or framework layout.
|
|
15
|
+
7. **Model facts have one authority chain.** `/models`, CLI JSON, runtime selection, and usage receipts must follow the normative model SSOT contract in `protocol/models/README.md`; provider facts retain authoritative provenance while AgentSam recommendations remain separate policy.
|
|
15
16
|
|
|
16
17
|
## Layout
|
|
17
18
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.inneranimalmedia.com/agentsam/capabilities/cloudflare-cpu-audit-input.schema.json",
|
|
4
|
+
"title": "cloudflare.cpu.audit input",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["file"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"cwd": { "type": "string", "minLength": 1 },
|
|
10
|
+
"file": { "type": "string", "minLength": 1 },
|
|
11
|
+
"sources": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"maxItems": 12,
|
|
14
|
+
"items": { "type": "string", "minLength": 1 }
|
|
15
|
+
},
|
|
16
|
+
"maxFrames": { "type": "integer", "minimum": 1, "maximum": 100 },
|
|
17
|
+
"maxSourceChars": { "type": "integer", "minimum": 1000, "maximum": 64000, "default": 24000 }
|
|
18
|
+
}
|
|
19
|
+
}
|