@inneranimalmedia/agentsam-sdk 2.6.3 → 2.6.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.
- package/AGENTSAM.md +6 -0
- package/README.md +16 -1
- package/bin/agentsam +16 -1
- package/docs/BRAND_INTELLIGENCE.md +1 -1
- package/docs/architecture/AGENTSAM_DISTRIBUTION_OWNERSHIP.md +176 -0
- package/docs/architecture/AGENTSAM_GO_RUNTIME.md +282 -0
- package/docs/architecture/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/FS_E2E_CLOSURE_RECEIPT.md +59 -0
- package/docs/architecture/LOCAL_FS_AUTHORITY.md +38 -0
- package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +28 -0
- package/docs/architecture/PLAN_CLI_AND_LOCAL_STUDIO_DESKTOP.md +96 -0
- package/docs/architecture/SAM_ACTIVITY_RECOVERY_RECEIPT.md +39 -0
- package/docs/architecture/SAM_DECISION_WORK_RECEIPT.md +54 -0
- package/docs/architecture/SAM_KERNEL.md +181 -0
- package/docs/architecture/SAM_MACHINE_NORMALIZATION_PRECOMMIT_REPORT.md +208 -0
- package/docs/architecture/SLASH_SKILLS_PORTABLE.md +116 -0
- package/docs/architecture/fs-e2e-receipt.latest.json +42 -0
- package/docs/architecture/previews/codebaseindex-guided-path/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/previews/codebaseindex-guided-path/index.html +321 -0
- package/migrations/d1/0010_portable_tickets_memory.sql +140 -0
- package/migrations/d1/0011_agentsam_skill_v2.sql +185 -0
- package/migrations/d1/0011b_agentsam_skill_v2_cutover.sql +22 -0
- package/migrations/d1/0011c_agentsam_skill_v2_backfill.sql +76 -0
- package/migrations/d1/0011d_agentsam_skill_v2_retrieval_revisions.sql +50 -0
- package/migrations/d1/0012_agentsam_tools_required_seed.sql +67 -0
- package/migrations/d1/0013_identity_oauth_states.sql +15 -0
- package/migrations/d1/0014_auth_event_log.sql +20 -0
- package/migrations/d1/0015_identity_oauth_state_app_id.sql +3 -0
- package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +16 -0
- package/migrations/sqlite/agentsam_skill_retrieval.portable.sql +42 -0
- package/package.json +19 -4
- package/packages/agentsam-contracts/src/errors.ts +16 -0
- package/packages/agentsam-errors/src/envelope.js +53 -0
- package/packages/agentsam-errors/src/index.js +1 -0
- package/packages/agentsam-errors/src/recovery.js +301 -0
- package/packages/agentsam-knowledge/src/providers/index.js +18 -6
- package/packages/connectors/cloudflare/src/routes.js +9 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +11 -2
- package/packages/identity/docs/PORTABLE_IDENTITY_ARCHITECTURE.md +50 -0
- package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +31 -0
- package/packages/identity/migrations/sqlite/001_identity_core.sql +99 -0
- package/packages/identity/migrations/sqlite/002_identity_oauth_client.sql +38 -0
- package/packages/identity/migrations/sqlite/003_identity_oauth_server.sql +64 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/adapters/cloudflare-d1/index.js +122 -22
- package/packages/identity/src/adapters/sqlite/index.js +319 -0
- package/packages/identity/src/app/verify-app.js +95 -0
- package/packages/identity/src/contracts/identity-store.js +115 -0
- package/packages/identity/src/contracts/route-ids.js +30 -0
- package/packages/identity/src/contracts/route-projection.js +218 -0
- package/packages/identity/src/contracts/routes.js +11 -0
- package/packages/identity/src/core/browser-paths.js +4 -5
- package/packages/identity/src/core/constants.js +13 -8
- package/packages/identity/src/core/session-policy.js +32 -0
- package/packages/identity/src/frontend/auth-portal/pages/login.html +10 -10
- package/packages/identity/src/frontend/auth-portal/pages/reset.html +3 -3
- package/packages/identity/src/frontend/auth-portal/pages/signup.html +3 -3
- package/packages/identity/src/frontend/auth-portal/preview/dashboard-stub.html +1 -1
- package/packages/identity/src/index.js +25 -0
- package/packages/identity/src/oauth/README.md +10 -4
- package/packages/identity/src/oauth/credentials.js +20 -13
- package/packages/identity/src/oauth/finalize-inbound.js +1 -1
- package/packages/identity/src/oauth/iam-platform.js +8 -7
- package/packages/identity/src/oauth/redirect-paths.js +27 -36
- package/packages/identity/src/server/identity-service.js +30 -13
- package/packages/identity/src/server/mount-policy.js +30 -0
- package/packages/identity/src/server/post-auth.js +79 -0
- package/packages/identity/src/server/worker-router.js +104 -72
- package/packages/identity/tests/finalize-inbound-oauth.test.mjs +6 -6
- package/packages/identity/tests/iam-provider.test.mjs +1 -1
- package/packages/identity/tests/identity-service.test.mjs +36 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +3 -1
- package/packages/identity/tests/portable-identity-architecture.test.mjs +157 -0
- package/packages/identity/tests/session-routes-policy.test.mjs +21 -0
- package/packages/theme-church-site/package.json +2 -1
- package/packages/theme-church-site/src/index.js +1 -0
- package/packages/theme-companions-site/package.json +2 -1
- package/packages/theme-companions-site/src/index.js +1 -0
- package/packages/theme-floors-site/package.json +2 -1
- package/packages/theme-floors-site/src/index.js +1 -0
- package/packages/theme-fuelnfree-site/package.json +2 -1
- package/packages/theme-fuelnfree-site/src/index.js +1 -0
- package/packages/theme-handyman-site/package.json +2 -1
- package/packages/theme-handyman-site/src/index.js +1 -0
- package/packages/theme-insurance-site/package.json +2 -1
- package/packages/theme-insurance-site/src/index.js +1 -0
- package/packages/theme-shinshu-site/package.json +2 -1
- package/packages/theme-shinshu-site/src/index.js +1 -0
- package/protocol/apps/agentsam.app.v1.schema.json +51 -0
- package/protocol/brand/brandpack.v1.schema.json +43 -0
- package/protocol/credentials/issue.v1.schema.json +38 -0
- package/protocol/database/connection.v1.schema.json +41 -0
- package/protocol/embeddings/embedding-profile.v1.schema.json +20 -0
- package/protocol/errors/error-envelope.schema.json +135 -1
- package/protocol/errors/recovery.v1.schema.json +50 -0
- package/protocol/runtime/workspace-fs.v1.schema.json +71 -0
- package/protocol/sam/activity.v1.schema.json +48 -0
- package/protocol/sam/answer.v1.schema.json +35 -0
- package/protocol/sam/calibration.v1.schema.json +21 -0
- package/protocol/sam/decision-receipt.v1.schema.json +29 -0
- package/protocol/sam/evaluation.v1.schema.json +19 -0
- package/protocol/sam/operation.schema.json +66 -0
- package/protocol/sam/outcome.v1.schema.json +36 -0
- package/protocol/sam/question.v1.schema.json +26 -0
- package/protocol/sam/registry.seed.json +153 -0
- package/protocol/sam/result.schema.json +53 -0
- package/protocol/sam/state.v1.schema.json +23 -0
- package/protocol/skills/agentsam.interaction.v1.schema.json +50 -0
- package/protocol/skills/agentsam.skill.v1.schema.json +57 -0
- package/protocol/ui/icon-registry.mjs +226 -0
- package/protocol/ui/icon.v1.schema.json +51 -0
- package/skills/README.md +22 -9
- package/skills/agentsam-codebaseindex/SKILL.md +225 -0
- package/skills/catalog.json +14 -0
- package/src/cli/command-catalog.js +130 -0
- package/src/cli/dispatch.js +48 -0
- package/src/cli.js +43 -1
- package/src/commands/api-key.js +244 -0
- package/src/commands/app.js +60 -28
- package/src/commands/brand.js +17 -19
- package/src/commands/codebaseindex.js +688 -0
- package/src/commands/env.js +152 -25
- package/src/commands/go.js +366 -53
- package/src/commands/interaction-clack.js +115 -0
- package/src/commands/models.js +1 -1
- package/src/commands/providers.js +62 -14
- package/src/commands/shell.js +43 -2
- package/src/commands/skill.js +248 -0
- package/src/commands/skills.js +1 -1
- package/src/commands/start-local.js +4 -0
- package/src/commands/whoami.js +90 -18
- package/src/go/build.js +229 -39
- package/src/go/cloudflare.js +506 -105
- package/src/go/container.js +120 -0
- package/src/go/contract.js +9 -4
- package/src/go/discover.js +149 -33
- package/src/go/index.js +15 -3
- package/src/go/native-probe-runner.mjs +119 -0
- package/src/go/{registry.js → official-registry.js} +64 -7
- package/src/go/official-release.js +10 -0
- package/src/go/receipts.js +77 -10
- package/src/go/verify.js +9 -2
- package/src/index.js +27 -0
- package/src/indexing/ingest/discover-models.js +298 -0
- package/src/indexing/ingest/inventory.js +243 -0
- package/src/indexing/ingest/job-graph.js +181 -0
- package/src/indexing/ingest/materials.js +210 -0
- package/src/lib/provider-credentials.js +63 -21
- package/src/lib/slash-commands.js +1 -0
- package/src/local-fs/capability.js +121 -0
- package/src/local-fs/freshness.js +75 -0
- package/src/local-fs/index.js +385 -0
- package/src/local-fs/paths.js +100 -0
- package/src/local-pty/server.js +295 -19
- package/src/mcp/client.js +2 -2
- package/src/models/ai-access-onboarding.js +112 -0
- package/src/models/discovery.js +10 -2
- package/src/models/inventory-core.js +9 -1
- package/src/sam/activity/index.js +183 -0
- package/src/sam/client.js +252 -0
- package/src/sam/decision/calibration.js +109 -0
- package/src/sam/decision/confidence.js +126 -0
- package/src/sam/decision/evaluate.js +157 -0
- package/src/sam/decision/evaluators/deterministic.js +341 -0
- package/src/sam/decision/evaluators/heuristic.js +61 -0
- package/src/sam/decision/evaluators/select.js +50 -0
- package/src/sam/decision/evaluators/semantic.js +149 -0
- package/src/sam/decision/hierarchical.js +61 -0
- package/src/sam/decision/index.js +53 -0
- package/src/sam/decision/policy.js +86 -0
- package/src/sam/decision/questions.js +120 -0
- package/src/sam/decision/receipt.js +148 -0
- package/src/sam/decision/state.js +117 -0
- package/src/sam/decision/types.js +22 -0
- package/src/sam/decision/validate.js +200 -0
- package/src/sam/define.js +51 -0
- package/src/sam/index.js +65 -0
- package/src/sam/operations/brand-scan.js +62 -0
- package/src/sam/operations/cad-blender-inspect.js +36 -0
- package/src/sam/operations/codebaseindex-ingest.js +49 -0
- package/src/sam/operations/decision-evaluate.js +59 -0
- package/src/sam/operations/planning-astar.js +77 -0
- package/src/sam/operations/planning-goap.js +60 -0
- package/src/sam/operations/repository-inspect.js +72 -0
- package/src/sam/operations/security-scan.js +33 -0
- package/src/sam/operations/terminal-exec.js +29 -0
- package/src/sam/planning/astar.js +311 -0
- package/src/sam/planning/goap.js +177 -0
- package/src/sam/planning/index.js +21 -0
- package/src/sam/planning/state.js +84 -0
- package/src/sam/registry.js +48 -0
- package/src/sam/result.js +77 -0
- package/src/sam/seed.js +44 -0
- package/src/sam/types.js +91 -0
- package/src/skills/catalog.js +64 -0
- package/src/skills/content-resolver.js +124 -0
- package/src/skills/hosted-store.js +37 -0
- package/src/skills/index.js +29 -64
- package/src/skills/interaction.js +102 -0
- package/src/skills/local-store.js +228 -0
- package/src/skills/manifest.js +104 -0
- package/src/skills/metrics.js +31 -0
- package/src/skills/registry.js +184 -0
- package/src/skills/runtime.js +287 -0
- package/src/skills/slash.js +44 -0
- package/src/ui/cli/help.js +94 -101
- package/test/cli/api-key-env-whoami.test.mjs +129 -0
- package/test/cli/codebaseindex-plan-ux.test.mjs +37 -0
- package/test/cli/go.test.mjs +62 -5
- package/test/cli/skill-npm-and-env.test.mjs +52 -0
- package/test/cli/wireframes-go-registry.test.mjs +87 -2
- package/test/go/build-source-identity.test.mjs +31 -0
- package/test/go/cloudflare-probe.test.mjs +274 -10
- package/test/go/distribution.test.mjs +28 -0
- package/test/integration/ai-access-onboarding.test.mjs +47 -0
- package/test/integration/cli-help.test.mjs +1 -1
- package/test/integration/cms-site-tenancy-contract.test.mjs +6 -6
- package/test/integration/codebaseindex-ingest.test.mjs +166 -0
- package/test/integration/icon-registry.test.mjs +67 -0
- package/test/integration/ingest-discover-models.test.mjs +30 -0
- package/test/integration/install-script.test.mjs +12 -9
- package/test/integration/local-fs.test.mjs +113 -0
- package/test/integration/provider-env-cli.test.mjs +2 -1
- package/test/integration/sam-activity-recovery.test.mjs +147 -0
- package/test/integration/sam-decision.test.mjs +584 -0
- package/test/integration/sam-kernel.test.mjs +99 -0
- package/test/integration/sam-planning-astar.test.mjs +279 -0
- package/test/integration/skill-runtime.test.mjs +240 -0
- package/test/integration/studio-fs-pty-e2e.test.mjs +294 -0
- package/test/models.test.mjs +14 -7
- package/test/shell.test.mjs +4 -4
|
@@ -166,6 +166,59 @@ export function createErrorEnvelope(input = {}) {
|
|
|
166
166
|
if (!Number.isInteger(envelope.http_status) || envelope.http_status < 100 || envelope.http_status > 599) envelope.http_status = null;
|
|
167
167
|
if (!Number.isInteger(envelope.grpc_status) || envelope.grpc_status < 0 || envelope.grpc_status > 16) envelope.grpc_status = null;
|
|
168
168
|
envelope.fingerprint = optional(input.fingerprint, 128) || fingerprintError(envelope);
|
|
169
|
+
|
|
170
|
+
// Optional recovery dimensions (v1-compatible extras — orthogonal to reason enum)
|
|
171
|
+
const failureClass = input.failure_class || null;
|
|
172
|
+
if (failureClass) envelope.failure_class = clean(failureClass);
|
|
173
|
+
if (input.operation && typeof input.operation === 'object') {
|
|
174
|
+
envelope.operation = Object.freeze({
|
|
175
|
+
kind: optional(input.operation.kind, 256),
|
|
176
|
+
action: optional(input.operation.action, 256),
|
|
177
|
+
resource_type: optional(input.operation.resource_type, 128),
|
|
178
|
+
resource_id: optional(input.operation.resource_id, 512),
|
|
179
|
+
read_only: input.operation.read_only === true,
|
|
180
|
+
idempotent: input.operation.idempotent === true,
|
|
181
|
+
side_effect_state: optional(input.operation.side_effect_state, 64) || 'unknown',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (input.retry && typeof input.retry === 'object') {
|
|
185
|
+
envelope.retry = Object.freeze({
|
|
186
|
+
allowed: input.retry.allowed == null ? retryable : Boolean(input.retry.allowed),
|
|
187
|
+
strategy: optional(input.retry.strategy, 64) || (retryable ? 'exponential_backoff' : 'none'),
|
|
188
|
+
retry_after_ms: input.retry.retry_after_ms == null ? envelope.retry_after_ms : Math.max(0, Math.round(Number(input.retry.retry_after_ms) || 0)),
|
|
189
|
+
max_attempts: Number.isInteger(input.retry.max_attempts) ? input.retry.max_attempts : null,
|
|
190
|
+
requires_idempotency_key: input.retry.requires_idempotency_key === true,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (input.fallback && typeof input.fallback === 'object') {
|
|
194
|
+
envelope.fallback = Object.freeze({
|
|
195
|
+
allowed: Boolean(input.fallback.allowed),
|
|
196
|
+
strategy: optional(input.fallback.strategy, 64),
|
|
197
|
+
preserve_semantics: input.fallback.preserve_semantics !== false,
|
|
198
|
+
target: optional(input.fallback.target, 256),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (input.notify && typeof input.notify === 'object') {
|
|
202
|
+
envelope.notify = Object.freeze({
|
|
203
|
+
user: optional(input.notify.user, 64) || 'immediately',
|
|
204
|
+
operator: optional(input.notify.operator, 64) || 'never',
|
|
205
|
+
agent: optional(input.notify.agent, 64) || 'stop',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (input.cause && typeof input.cause === 'object') {
|
|
209
|
+
envelope.cause = input.cause;
|
|
210
|
+
}
|
|
211
|
+
if (input.status === 'partial' || input.status === 'success' || input.status === 'failure') {
|
|
212
|
+
envelope.status = input.status;
|
|
213
|
+
}
|
|
214
|
+
if (input.completed != null || input.failed != null) {
|
|
215
|
+
envelope.partial = Object.freeze({
|
|
216
|
+
completed: Number(input.completed) || 0,
|
|
217
|
+
failed: Number(input.failed) || 0,
|
|
218
|
+
failures: Array.isArray(input.failures) ? input.failures : [],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
169
222
|
return deepFreeze(envelope);
|
|
170
223
|
}
|
|
171
224
|
|
|
@@ -5,6 +5,7 @@ export * from './normalize.js';
|
|
|
5
5
|
export * from './redaction.js';
|
|
6
6
|
export * from './fingerprint.js';
|
|
7
7
|
export * from './retry.js';
|
|
8
|
+
export * from './recovery.js';
|
|
8
9
|
export * from './transport.js';
|
|
9
10
|
export * from './render.js';
|
|
10
11
|
export * from './adapters/index.js';
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovery policy — separates "what failed" from "what to do".
|
|
3
|
+
*
|
|
4
|
+
* ErrorEnvelope → planRecovery → RecoveryPlan → SAM executes
|
|
5
|
+
*
|
|
6
|
+
* Critical rule: unknown side_effect_state ⇒ reconcile before retry.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const FAILURE_CLASS = Object.freeze({
|
|
10
|
+
INPUT: 'input',
|
|
11
|
+
AUTHENTICATION: 'authentication',
|
|
12
|
+
AUTHORIZATION: 'authorization',
|
|
13
|
+
POLICY: 'policy',
|
|
14
|
+
DISCOVERY: 'discovery',
|
|
15
|
+
RESOLUTION: 'resolution',
|
|
16
|
+
TRANSPORT: 'transport',
|
|
17
|
+
PROTOCOL: 'protocol',
|
|
18
|
+
TIMEOUT: 'timeout',
|
|
19
|
+
RATE_LIMIT: 'rate_limit',
|
|
20
|
+
CAPACITY: 'capacity',
|
|
21
|
+
RESOURCE: 'resource',
|
|
22
|
+
FILESYSTEM: 'filesystem',
|
|
23
|
+
PERSISTENCE: 'persistence',
|
|
24
|
+
CONSISTENCY: 'consistency',
|
|
25
|
+
CONFLICT: 'conflict',
|
|
26
|
+
DEPENDENCY: 'dependency',
|
|
27
|
+
PROCESS: 'process',
|
|
28
|
+
IPC: 'ipc',
|
|
29
|
+
DATA_INTEGRITY: 'data_integrity',
|
|
30
|
+
CANCELLED: 'cancelled',
|
|
31
|
+
UNKNOWN: 'unknown',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const SIDE_EFFECT_STATE = Object.freeze({
|
|
35
|
+
NONE: 'none',
|
|
36
|
+
NOT_STARTED: 'not_started',
|
|
37
|
+
CONFIRMED_NOT_APPLIED: 'confirmed_not_applied',
|
|
38
|
+
CONFIRMED_APPLIED: 'confirmed_applied',
|
|
39
|
+
PARTIALLY_APPLIED: 'partially_applied',
|
|
40
|
+
UNKNOWN: 'unknown',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const RECOVERY_SCHEMA = 'agentsam.recovery.v1';
|
|
44
|
+
|
|
45
|
+
const NO_RETRY_OWNERS = new Set(['policy']);
|
|
46
|
+
const CANCEL_REASONS = new Set([
|
|
47
|
+
'cancelled_by_user',
|
|
48
|
+
'cancelled_by_agent',
|
|
49
|
+
'cancelled_by_policy',
|
|
50
|
+
'cancelled_by_timeout',
|
|
51
|
+
'cancelled_by_parent',
|
|
52
|
+
'cancelled',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Infer failure_class from reason/code when not supplied.
|
|
57
|
+
* @param {object} error
|
|
58
|
+
*/
|
|
59
|
+
export function inferFailureClass(error = {}) {
|
|
60
|
+
const given = error.failure_class;
|
|
61
|
+
if (given && Object.values(FAILURE_CLASS).includes(given)) return given;
|
|
62
|
+
const reason = String(error.reason || '');
|
|
63
|
+
const code = String(error.code || '');
|
|
64
|
+
if (CANCEL_REASONS.has(reason) || code === 'CANCELLED') return FAILURE_CLASS.CANCELLED;
|
|
65
|
+
if (/^auth_|unauthenticated|credential/.test(reason) || code === 'UNAUTHENTICATED') return FAILURE_CLASS.AUTHENTICATION;
|
|
66
|
+
if (/permission|authorization|capability/.test(reason) || code === 'PERMISSION_DENIED') return FAILURE_CLASS.AUTHORIZATION;
|
|
67
|
+
if (/policy/.test(reason)) return FAILURE_CLASS.POLICY;
|
|
68
|
+
if (/rate_limit|resource_exhausted|quota|budget/.test(reason) || code === 'RESOURCE_EXHAUSTED') return FAILURE_CLASS.RATE_LIMIT;
|
|
69
|
+
if (/timeout|deadline/.test(reason) || code === 'DEADLINE_EXCEEDED') return FAILURE_CLASS.TIMEOUT;
|
|
70
|
+
if (/^fs_|filesystem|path_escape|version_conflict/.test(reason)) return FAILURE_CLASS.FILESYSTEM;
|
|
71
|
+
if (/^db_|persistence|sqlite|d1/.test(reason)) return FAILURE_CLASS.PERSISTENCE;
|
|
72
|
+
if (/conflict|etag|precondition|serialization/.test(reason) || code === 'ABORTED') return FAILURE_CLASS.CONFLICT;
|
|
73
|
+
if (/^pty_|process_|ipc_/.test(reason)) return FAILURE_CLASS.PROCESS;
|
|
74
|
+
if (/transport|connection|dns|tls|websocket|grpc_unavailable/.test(reason) || code === 'UNAVAILABLE') {
|
|
75
|
+
return FAILURE_CLASS.TRANSPORT;
|
|
76
|
+
}
|
|
77
|
+
if (/corrupt|checksum|integrity|data_loss/.test(reason) || code === 'DATA_LOSS') return FAILURE_CLASS.DATA_INTEGRITY;
|
|
78
|
+
if (/input_|invalid_argument|malformed/.test(reason) || code === 'INVALID_ARGUMENT') return FAILURE_CLASS.INPUT;
|
|
79
|
+
return FAILURE_CLASS.UNKNOWN;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Infer side_effect_state for the failed operation.
|
|
84
|
+
* @param {object} error
|
|
85
|
+
* @param {object} [operationContext]
|
|
86
|
+
*/
|
|
87
|
+
export function inferSideEffectState(error = {}, operationContext = {}) {
|
|
88
|
+
const op = error.operation || operationContext.operation || {};
|
|
89
|
+
if (op.side_effect_state) return op.side_effect_state;
|
|
90
|
+
if (op.read_only === true || op.side_effect_state === SIDE_EFFECT_STATE.NONE) {
|
|
91
|
+
return SIDE_EFFECT_STATE.NONE;
|
|
92
|
+
}
|
|
93
|
+
if (op.idempotent === true && error.retryable) {
|
|
94
|
+
return SIDE_EFFECT_STATE.CONFIRMED_NOT_APPLIED;
|
|
95
|
+
}
|
|
96
|
+
// Timeouts / connection drops on mutating ops → outcome unknown
|
|
97
|
+
const reason = String(error.reason || '');
|
|
98
|
+
const code = String(error.code || '');
|
|
99
|
+
if (
|
|
100
|
+
code === 'DEADLINE_EXCEEDED'
|
|
101
|
+
|| /timeout|connection_reset|connection_closed|truncated/.test(reason)
|
|
102
|
+
) {
|
|
103
|
+
if (op.read_only) return SIDE_EFFECT_STATE.NONE;
|
|
104
|
+
return SIDE_EFFECT_STATE.UNKNOWN;
|
|
105
|
+
}
|
|
106
|
+
if (error.retryable === false && /permission|auth|input|policy/.test(reason)) {
|
|
107
|
+
return SIDE_EFFECT_STATE.CONFIRMED_NOT_APPLIED;
|
|
108
|
+
}
|
|
109
|
+
return op.kind ? SIDE_EFFECT_STATE.UNKNOWN : SIDE_EFFECT_STATE.NONE;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Central recovery planner — one policy for CLI / Worker / Studio / MCP.
|
|
114
|
+
*
|
|
115
|
+
* @param {object} error ErrorEnvelope (v1+)
|
|
116
|
+
* @param {object} [operationContext]
|
|
117
|
+
* @returns {object} RecoveryPlan
|
|
118
|
+
*/
|
|
119
|
+
export function planRecovery(error = {}, operationContext = {}) {
|
|
120
|
+
const failure_class = inferFailureClass(error);
|
|
121
|
+
const side_effect_state = inferSideEffectState(error, operationContext);
|
|
122
|
+
const attempt = Number(operationContext.attempt || error.retry?.attempt || 1);
|
|
123
|
+
const maxAttempts = Number(
|
|
124
|
+
operationContext.max_attempts
|
|
125
|
+
?? error.retry?.max_attempts
|
|
126
|
+
?? (error.retryable ? 4 : 1),
|
|
127
|
+
);
|
|
128
|
+
const evidence = [];
|
|
129
|
+
evidence.push(`failure_class=${failure_class}`);
|
|
130
|
+
evidence.push(`side_effect_state=${side_effect_state}`);
|
|
131
|
+
evidence.push(`reason=${error.reason || 'unknown'}`);
|
|
132
|
+
|
|
133
|
+
/** @type {object} */
|
|
134
|
+
let plan = {
|
|
135
|
+
schema: RECOVERY_SCHEMA,
|
|
136
|
+
error_fingerprint: error.fingerprint || null,
|
|
137
|
+
disposition: 'abort',
|
|
138
|
+
attempts: { current: attempt, maximum: maxAttempts },
|
|
139
|
+
delay_ms: null,
|
|
140
|
+
fallback: null,
|
|
141
|
+
notify: {
|
|
142
|
+
user: 'immediately',
|
|
143
|
+
operator: 'never',
|
|
144
|
+
agent: 'stop',
|
|
145
|
+
},
|
|
146
|
+
reason: error.reason || 'unknown',
|
|
147
|
+
evidence,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Cancellations are not defects
|
|
151
|
+
if (failure_class === FAILURE_CLASS.CANCELLED) {
|
|
152
|
+
return freezePlan({
|
|
153
|
+
...plan,
|
|
154
|
+
disposition: 'abort',
|
|
155
|
+
notify: { user: 'never', operator: 'never', agent: 'stop' },
|
|
156
|
+
reason: 'cancelled',
|
|
157
|
+
evidence: [...evidence, 'cancellation_is_not_a_defect'],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Ambiguous side effects: NEVER blind retry
|
|
162
|
+
if (
|
|
163
|
+
side_effect_state === SIDE_EFFECT_STATE.UNKNOWN
|
|
164
|
+
|| side_effect_state === SIDE_EFFECT_STATE.PARTIALLY_APPLIED
|
|
165
|
+
) {
|
|
166
|
+
return freezePlan({
|
|
167
|
+
...plan,
|
|
168
|
+
disposition: 'reconcile',
|
|
169
|
+
notify: { user: 'immediately', operator: 'warning', agent: 'wait' },
|
|
170
|
+
reason: 'side_effect_uncertain',
|
|
171
|
+
evidence: [...evidence, 'reconcile_before_retry'],
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Policy / auth hard stops
|
|
176
|
+
if (
|
|
177
|
+
failure_class === FAILURE_CLASS.POLICY
|
|
178
|
+
|| failure_class === FAILURE_CLASS.AUTHORIZATION
|
|
179
|
+
|| NO_RETRY_OWNERS.has(error.resolution_owner)
|
|
180
|
+
) {
|
|
181
|
+
return freezePlan({
|
|
182
|
+
...plan,
|
|
183
|
+
disposition: 'await_user',
|
|
184
|
+
notify: { user: 'immediately', operator: 'never', agent: 'wait' },
|
|
185
|
+
reason: 'policy_or_auth_blocks_retry',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (failure_class === FAILURE_CLASS.AUTHENTICATION) {
|
|
190
|
+
return freezePlan({
|
|
191
|
+
...plan,
|
|
192
|
+
disposition: 'await_user',
|
|
193
|
+
notify: { user: 'immediately', operator: 'never', agent: 'wait' },
|
|
194
|
+
reason: 'authentication_required',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (failure_class === FAILURE_CLASS.INPUT || failure_class === FAILURE_CLASS.CONFLICT) {
|
|
199
|
+
const disposition = failure_class === FAILURE_CLASS.CONFLICT && error.retryable
|
|
200
|
+
? 'retry'
|
|
201
|
+
: 'await_user';
|
|
202
|
+
return freezePlan({
|
|
203
|
+
...plan,
|
|
204
|
+
disposition,
|
|
205
|
+
delay_ms: disposition === 'retry' ? 0 : null,
|
|
206
|
+
attempts: { current: attempt, maximum: Math.min(maxAttempts, 2) },
|
|
207
|
+
notify: {
|
|
208
|
+
user: disposition === 'await_user' ? 'immediately' : 'after_retry_exhausted',
|
|
209
|
+
operator: 'never',
|
|
210
|
+
agent: disposition === 'retry' ? 'retry' : 'wait',
|
|
211
|
+
},
|
|
212
|
+
reason: failure_class === FAILURE_CLASS.CONFLICT ? 'optimistic_concurrency' : 'invalid_input',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Safe retry path
|
|
217
|
+
if (error.retryable && attempt < maxAttempts) {
|
|
218
|
+
const delay = retryDelayFor(error, attempt, failure_class);
|
|
219
|
+
return freezePlan({
|
|
220
|
+
...plan,
|
|
221
|
+
disposition: failure_class === FAILURE_CLASS.TRANSPORT || /pty_|websocket|connection/.test(String(error.reason))
|
|
222
|
+
? 'reconnect'
|
|
223
|
+
: 'retry',
|
|
224
|
+
delay_ms: delay,
|
|
225
|
+
notify: {
|
|
226
|
+
user: attempt >= 2 ? 'on_degradation' : 'never',
|
|
227
|
+
operator: 'never',
|
|
228
|
+
agent: 'retry',
|
|
229
|
+
},
|
|
230
|
+
reason: 'retry_budget_remaining',
|
|
231
|
+
evidence: [...evidence, `delay_ms=${delay}`],
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Fallback if permitted and semantics-preserving
|
|
236
|
+
const fallbackAllowed = error.fallback?.allowed === true
|
|
237
|
+
|| operationContext.fallback_allowed === true;
|
|
238
|
+
const preserve = error.fallback?.preserve_semantics !== false
|
|
239
|
+
&& operationContext.preserve_semantics !== false;
|
|
240
|
+
if (fallbackAllowed && preserve) {
|
|
241
|
+
return freezePlan({
|
|
242
|
+
...plan,
|
|
243
|
+
disposition: 'fallback',
|
|
244
|
+
fallback: {
|
|
245
|
+
kind: error.fallback?.strategy || operationContext.fallback_strategy || 'degraded_mode',
|
|
246
|
+
target: error.fallback?.target || operationContext.fallback_target || null,
|
|
247
|
+
},
|
|
248
|
+
notify: { user: 'on_degradation', operator: 'warning', agent: 'continue' },
|
|
249
|
+
reason: 'semantic_fallback_allowed',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Replan if GOAP/A* context available
|
|
254
|
+
if (operationContext.replan_allowed) {
|
|
255
|
+
return freezePlan({
|
|
256
|
+
...plan,
|
|
257
|
+
disposition: 'replan',
|
|
258
|
+
notify: { user: 'on_degradation', operator: 'never', agent: 'replan' },
|
|
259
|
+
reason: 'retry_exhausted_replan',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Terminal block
|
|
264
|
+
return freezePlan({
|
|
265
|
+
...plan,
|
|
266
|
+
disposition: error.resolution_owner === 'user' ? 'await_user' : 'abort',
|
|
267
|
+
notify: {
|
|
268
|
+
user: 'immediately',
|
|
269
|
+
operator: error.severity === 'blocking_internal' ? 'incident' : 'warning',
|
|
270
|
+
agent: 'stop',
|
|
271
|
+
},
|
|
272
|
+
reason: 'recovery_exhausted',
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function retryDelayFor(error, attempt, failureClass) {
|
|
277
|
+
if (Number.isFinite(error.retry_after_ms) && error.retry_after_ms >= 0) {
|
|
278
|
+
return Math.round(error.retry_after_ms);
|
|
279
|
+
}
|
|
280
|
+
if (failureClass === FAILURE_CLASS.RATE_LIMIT) {
|
|
281
|
+
return Math.min(60_000, Math.round(1000 * (2 ** Math.min(6, attempt - 1))));
|
|
282
|
+
}
|
|
283
|
+
if (failureClass === FAILURE_CLASS.TRANSPORT || failureClass === FAILURE_CLASS.TIMEOUT) {
|
|
284
|
+
const base = 250;
|
|
285
|
+
const capped = Math.min(8_000, base * (2 ** Math.min(5, attempt - 1)));
|
|
286
|
+
const jitter = Math.floor(Math.random() * Math.max(50, capped * 0.2));
|
|
287
|
+
return capped + jitter;
|
|
288
|
+
}
|
|
289
|
+
if (failureClass === FAILURE_CLASS.CONFLICT) return 0;
|
|
290
|
+
return Math.min(4_000, 500 * (2 ** Math.min(4, attempt - 1)));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function freezePlan(plan) {
|
|
294
|
+
return Object.freeze({
|
|
295
|
+
...plan,
|
|
296
|
+
attempts: Object.freeze({ ...plan.attempts }),
|
|
297
|
+
notify: Object.freeze({ ...plan.notify }),
|
|
298
|
+
fallback: plan.fallback ? Object.freeze({ ...plan.fallback }) : null,
|
|
299
|
+
evidence: Object.freeze([...(plan.evidence || [])]),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
@@ -6,13 +6,13 @@ const finiteVector = (vector, dimensions) => {
|
|
|
6
6
|
return vector;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
function httpAdapter({ id, env, models, request, credentials = env }) {
|
|
9
|
+
function httpAdapter({ id, env, models, request, credentials = env, allowAnyModel = false }) {
|
|
10
10
|
return Object.freeze({
|
|
11
11
|
id,
|
|
12
12
|
capabilities: () => ({ id, operational: Boolean(credentials()), models, credentials: credentials() ? 'available' : 'missing' }),
|
|
13
13
|
validate(profile) {
|
|
14
14
|
if (clean(profile?.provider) !== id) throw new Error(`provider_profile_mismatch:${id}`);
|
|
15
|
-
if (!models.includes(clean(profile?.model))) throw new Error(`provider_model_unsupported:${id}:${profile?.model}`);
|
|
15
|
+
if (!allowAnyModel && !models.includes(clean(profile?.model))) throw new Error(`provider_model_unsupported:${id}:${profile?.model}`);
|
|
16
16
|
if (!Number.isInteger(profile?.dimensions) || profile.dimensions < 1) throw new Error('provider_dimensions_required');
|
|
17
17
|
if (!credentials()) throw new Error(`provider_credentials_unavailable:${id}`);
|
|
18
18
|
},
|
|
@@ -70,13 +70,25 @@ export function createWorkersAiProvider({ binding } = {}) {
|
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
export function createOllamaProvider({ endpoint = process.env.OLLAMA_HOST || 'http://127.0.0.1:11434', fetchImpl = globalThis.fetch } = {}) {
|
|
74
|
-
|
|
73
|
+
export function createOllamaProvider({ endpoint = process.env.OLLAMA_HOST || 'http://127.0.0.1:11434', fetchImpl = globalThis.fetch, models } = {}) {
|
|
74
|
+
// Model allowlist is advisory only — live `ollama list` / tags are authoritative at selection time.
|
|
75
|
+
const known = Array.isArray(models) && models.length ? models : ['mxbai-embed-large', 'nomic-embed-text'];
|
|
76
|
+
return httpAdapter({
|
|
77
|
+
id: 'ollama',
|
|
78
|
+
env: 'OLLAMA_HOST',
|
|
79
|
+
models: known,
|
|
80
|
+
allowAnyModel: true,
|
|
81
|
+
credentials: () => endpoint,
|
|
75
82
|
request: async (text, profile) => {
|
|
76
|
-
const response = await fetchImpl(new URL('/api/embed', endpoint), {
|
|
83
|
+
const response = await fetchImpl(new URL('/api/embed', endpoint), {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { 'content-type': 'application/json' },
|
|
86
|
+
body: JSON.stringify({ model: profile.model, input: text }),
|
|
87
|
+
});
|
|
77
88
|
if (!response.ok) throw new Error(`provider_request_failed:ollama:${response.status}`);
|
|
78
89
|
return finiteVector((await response.json()).embeddings?.[0], profile.dimensions);
|
|
79
|
-
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
export function createProviderRegistry(options = {}) {
|
|
@@ -220,6 +220,15 @@ export async function handleCloudflareConnectionRequest(request, env) {
|
|
|
220
220
|
state,
|
|
221
221
|
codeChallenge: challenge,
|
|
222
222
|
});
|
|
223
|
+
// Browser navigation → 302 to Cloudflare. XHR/fetch (Integrations UI) → JSON.
|
|
224
|
+
const accept = String(request.headers.get('accept') || '');
|
|
225
|
+
const mode = String(request.headers.get('sec-fetch-mode') || '');
|
|
226
|
+
const wantsJson = accept.includes('application/json')
|
|
227
|
+
|| mode === 'cors'
|
|
228
|
+
|| request.headers.get('x-agentsam-oauth') === 'json';
|
|
229
|
+
if (!wantsJson) {
|
|
230
|
+
return Response.redirect(authorize, 302);
|
|
231
|
+
}
|
|
223
232
|
return json({ ok: true, authorize_url: authorize, callback: CLOUDFLARE_CALLBACK_PATH });
|
|
224
233
|
}
|
|
225
234
|
|
|
@@ -64,7 +64,10 @@ describe('cloudflare connector', () => {
|
|
|
64
64
|
};
|
|
65
65
|
const response = await handleCloudflareConnectionRequest(
|
|
66
66
|
new Request('https://agentsam.example/api/connections/cloudflare/start?return_to=https://untrusted.example/after', {
|
|
67
|
-
headers: {
|
|
67
|
+
headers: {
|
|
68
|
+
authorization: 'Bearer fixture',
|
|
69
|
+
accept: 'application/json',
|
|
70
|
+
},
|
|
68
71
|
}),
|
|
69
72
|
{
|
|
70
73
|
DB,
|
|
@@ -82,6 +85,28 @@ describe('cloudflare connector', () => {
|
|
|
82
85
|
assert.equal(stateInsert.args.at(-1), null);
|
|
83
86
|
});
|
|
84
87
|
|
|
88
|
+
it('redirects browser starts to Cloudflare authorization', async () => {
|
|
89
|
+
const oauthState = new Map();
|
|
90
|
+
const response = await handleCloudflareConnectionRequest(
|
|
91
|
+
new Request('https://agentsam.example/api/connections/cloudflare/start', {
|
|
92
|
+
headers: { authorization: 'Bearer fixture' },
|
|
93
|
+
}),
|
|
94
|
+
{
|
|
95
|
+
fixtureSessions: new Map([['fixture', 'user_123']]),
|
|
96
|
+
oauthState,
|
|
97
|
+
CLOUDFLARE_OAUTH_CLIENT_ID: 'real-client-id',
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
assert.equal(response.status, 302);
|
|
102
|
+
const location = new URL(response.headers.get('location'));
|
|
103
|
+
assert.equal(location.origin, 'https://dash.cloudflare.com');
|
|
104
|
+
assert.equal(location.pathname, '/oauth2/auth');
|
|
105
|
+
assert.equal(location.searchParams.get('client_id'), 'real-client-id');
|
|
106
|
+
assert.equal(location.searchParams.get('redirect_uri'), 'https://agentsam.example/api/connections/cloudflare/callback');
|
|
107
|
+
assert.equal(oauthState.size, 1);
|
|
108
|
+
});
|
|
109
|
+
|
|
85
110
|
it('consumes callback state and stores only encrypted OAuth tokens', async () => {
|
|
86
111
|
const now = Math.floor(Date.now() / 1000);
|
|
87
112
|
const calls = [];
|
|
@@ -46,11 +46,20 @@
|
|
|
46
46
|
"provider": "github"
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
|
-
"path": "/api/oauth/
|
|
49
|
+
"path": "/api/oauth/inneranimalmedia/start",
|
|
50
50
|
"kind": "api",
|
|
51
51
|
"access": "public",
|
|
52
52
|
"provider": "inneranimalmedia",
|
|
53
|
-
"oauth_provider_key": "iam"
|
|
53
|
+
"oauth_provider_key": "iam",
|
|
54
|
+
"aliases": ["/api/oauth/iam/start"]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"path": "/api/oauth/inneranimalmedia/callback",
|
|
58
|
+
"kind": "api",
|
|
59
|
+
"access": "public",
|
|
60
|
+
"provider": "inneranimalmedia",
|
|
61
|
+
"oauth_provider_key": "iam",
|
|
62
|
+
"aliases": ["/api/oauth/iam/callback"]
|
|
54
63
|
},
|
|
55
64
|
{
|
|
56
65
|
"path": "/api/oauth/cloudflare/start",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Portable Identity Architecture (pre-commit return)
|
|
2
|
+
|
|
3
|
+
## Invariants
|
|
4
|
+
|
|
5
|
+
1. Portable SDK owns **semantic route IDs** (`identity.login`), never host paths.
|
|
6
|
+
2. App manifests own **HTTP projections**.
|
|
7
|
+
3. Auth-capable apps MUST declare authenticated / login / recovery / OAuth callback.
|
|
8
|
+
4. OAuth **transactions** persist `app_id` + `return_to` (required).
|
|
9
|
+
5. Post-login: valid `return_to` → authenticated entry → recovery+error → **throw** (never `/`).
|
|
10
|
+
6. No `DASHBOARD_AFTER_LOGIN_PATH`, no `LOCAL_STUDIO_APP` in identity, no `APP_HOME_PATH` env.
|
|
11
|
+
7. Missing shell/route → package verification failure.
|
|
12
|
+
|
|
13
|
+
## Artifacts delivered
|
|
14
|
+
|
|
15
|
+
| Deliverable | Location |
|
|
16
|
+
|---|---|
|
|
17
|
+
| IdentityStore + errors | `packages/identity/src/contracts/identity-store.js` |
|
|
18
|
+
| Semantic IDs | `packages/identity/src/contracts/route-ids.js` |
|
|
19
|
+
| Route projection | `packages/identity/src/contracts/route-projection.js` |
|
|
20
|
+
| Fail-closed post-auth | `packages/identity/src/server/post-auth.js` |
|
|
21
|
+
| SESSION_POLICY | `packages/identity/src/core/session-policy.js` |
|
|
22
|
+
| SQLite core pack | `packages/identity/migrations/sqlite/001_identity_core.sql` |
|
|
23
|
+
| SQLite oauth-client | `packages/identity/migrations/sqlite/002_identity_oauth_client.sql` |
|
|
24
|
+
| SQLite oauth-server (optional) | `packages/identity/migrations/sqlite/003_identity_oauth_server.sql` |
|
|
25
|
+
| D1 mapping | `packages/identity/migrations/D1_SCHEMA_MAPPING.md` |
|
|
26
|
+
| createSqliteIdentityAdapter | `packages/identity/src/adapters/sqlite/index.js` |
|
|
27
|
+
| App verify | `packages/identity/src/app/verify-app.js` |
|
|
28
|
+
| App auth contract (Local Studio) | `apps/local-studio/agentsam.app.json` |
|
|
29
|
+
| Tests | `packages/identity/tests/portable-identity-architecture.test.mjs` |
|
|
30
|
+
|
|
31
|
+
## Redirect resolution
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
transaction.return_to → owned by app?
|
|
35
|
+
yes → use
|
|
36
|
+
no → app.authenticated
|
|
37
|
+
missing → identity.recovery?error=…
|
|
38
|
+
none → IdentityRoutingError('AUTH_DESTINATION_UNRESOLVED')
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Local-only path
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
applySqliteIdentityMigrations(db) // core + oauth-client
|
|
45
|
+
createSqliteIdentityAdapter(db)
|
|
46
|
+
createIdentityService({ adapter, app, routeRegistry })
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Zero Cloudflare / IAM / D1 required for password + session + OAuth transaction store.
|
|
50
|
+
Provider secrets → encrypted vault via `credential_ref` on `identity_provider_connections`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Identity schema mapping — portable ↔ Cloudflare D1
|
|
2
|
+
|
|
3
|
+
Portable SQLite (`packages/identity/migrations/sqlite/`) is the product schema.
|
|
4
|
+
The D1 adapter maps existing hosted table names without requiring a big-bang rename.
|
|
5
|
+
|
|
6
|
+
| Portable (SQLite) | Hosted D1 (current) | Notes |
|
|
7
|
+
|---|---|---|
|
|
8
|
+
| `identity_users` | `auth_users` | Same columns |
|
|
9
|
+
| `identity_external_accounts` | `account_identities` | `user_id` ↔ `account_id` |
|
|
10
|
+
| `identity_sessions` | `auth_sessions` | Same columns |
|
|
11
|
+
| `identity_auth_events` | `auth_event_log` | Same columns |
|
|
12
|
+
| `identity_oauth_transactions` | `identity_oauth_states` → migrate to `identity_oauth_transactions` | Requires `app_id NOT NULL`; no silent pre-app_id fallback |
|
|
13
|
+
| `identity_provider_connections` | (new / connector tables) | `credential_ref` → vault/KMS; never store raw tokens in D1 rows |
|
|
14
|
+
| `identity_app_registry` | cache of `agentsam.app.json` | Disk manifests remain SSOT |
|
|
15
|
+
| `identity_route_registry` | projection cache | Disk manifests remain SSOT |
|
|
16
|
+
| `identity_oauth_clients` … | optional `identity.oauth-server` pack | Only if product is an AS |
|
|
17
|
+
|
|
18
|
+
## Schema version
|
|
19
|
+
|
|
20
|
+
Both adapters read `identity_schema_meta.schema_version` (SQLite) or
|
|
21
|
+
`PRAGMA`/meta row. Missing `app_id` on OAuth transactions is an
|
|
22
|
+
`IdentitySchemaError('IDENTITY_SCHEMA_MIGRATION_REQUIRED')`, not a soft
|
|
23
|
+
downgrade.
|
|
24
|
+
|
|
25
|
+
## Packs
|
|
26
|
+
|
|
27
|
+
| Pack | Apply when |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `identity.core` | Always (local + hosted) |
|
|
30
|
+
| `identity.oauth-client` | Signing into external IdPs |
|
|
31
|
+
| `identity.oauth-server` | Product issues its own codes/tokens |
|