forgeos 0.1.0-alpha.27 → 0.1.0-alpha.29
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/AGENTS.md +1 -1
- package/CHANGELOG.md +18 -0
- package/docs/changelog.md +29 -0
- package/package.json +1 -1
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-adapters/index.ts +63 -18
- package/src/forge/agent-adapters/types.ts +6 -1
- package/src/forge/cli/auth.ts +56 -1
- package/src/forge/cli/authmd.ts +356 -0
- package/src/forge/cli/commands.ts +28 -0
- package/src/forge/cli/main.ts +6 -0
- package/src/forge/cli/output.ts +12 -0
- package/src/forge/cli/parse.ts +67 -1
- package/src/forge/cli/workos.ts +340 -0
- package/src/forge/compiler/agent-contract/build.ts +97 -3
- package/src/forge/compiler/agent-contract/types.ts +2 -1
- package/src/forge/compiler/emitter/render.ts +4 -0
- package/src/forge/compiler/integration/add.ts +1 -1
- package/src/forge/compiler/integration/plan.ts +15 -0
- package/src/forge/compiler/integration/render.ts +20 -0
- package/src/forge/compiler/integration/templates/index.ts +1 -0
- package/src/forge/compiler/integration/templates/render.ts +31 -0
- package/src/forge/compiler/integration/templates/workos.ts +1046 -0
- package/src/forge/compiler/orchestrator/plan.ts +10 -2
- package/src/forge/compiler/policy-registry/build.ts +3 -1
- package/src/forge/compiler/policy-registry/parse.ts +32 -2
- package/src/forge/compiler/recipes/definitions.ts +38 -0
- package/src/forge/compiler/recipes/index.ts +1 -0
- package/src/forge/compiler/recipes/registry.ts +3 -0
- package/src/forge/compiler/types/dev-manifest.ts +4 -0
- package/src/forge/compiler/types/emit.ts +2 -0
- package/src/forge/compiler/types/integration.ts +1 -0
- package/src/forge/compiler/types/policy-registry.ts +3 -1
- package/src/forge/dev/server.ts +519 -5
- package/src/forge/policy.ts +1 -1
- package/src/forge/runtime/auth/config.ts +17 -0
- package/src/forge/runtime/auth/evaluate.ts +15 -2
- package/src/forge/runtime/auth/resolve.ts +29 -4
- package/src/forge/runtime/webhooks/security.ts +12 -7
- package/src/forge/server.ts +5 -0
- package/src/forge/version.ts +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.29 input=25d84ce77a02f5c4ce49fa082fa968608c9859594d5876886344276ca0c27523 content=0d493cf0e41b71cb652d5e0e1b0c1f83d2a1281b748321f0b00f0773ba93074e
|
|
2
2
|
# AGENTS.md
|
|
3
3
|
|
|
4
4
|
<!-- forge-generated:start -->
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# forgeos
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.29
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add the first WorkOS/AuthKit adapter and local auth metadata tooling.
|
|
8
|
+
|
|
9
|
+
- Add `forge add auth workos`, generated WorkOS seed/config/docs, AuthKit routes, webhook handling, JWT/OIDC claim mapping, and permission-derived Forge policies.
|
|
10
|
+
- Add `forge authmd generate` and `forge authmd check`, including `/auth.md` and OAuth protected-resource metadata served by `forge dev`.
|
|
11
|
+
- Add a local WorkOS/FGA testkit, resource-graph helpers, cross-tenant guards, FGA cache/fallback telemetry, and mock multi-tenant regression coverage.
|
|
12
|
+
- Teach Forge auth and policies to understand permission claims alongside roles.
|
|
13
|
+
- Add `forge version --json` as a command alias and capture local helper table reads in the generated agent contract/capability map.
|
|
14
|
+
|
|
15
|
+
## 0.1.0-alpha.28
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Accept visible Codex hook canaries as sufficient for local editing while reporting native Codex provenance separately through `nativeTrustStatus`.
|
|
20
|
+
|
|
3
21
|
## 0.1.0-alpha.27
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/docs/changelog.md
CHANGED
|
@@ -6,6 +6,35 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.1.0-alpha.29
|
|
10
|
+
|
|
11
|
+
- Added the first WorkOS/AuthKit adapter surface: `forge add auth workos`
|
|
12
|
+
generates local AuthKit wiring, `.env.example`, `workos-seed.yml`, demo
|
|
13
|
+
organizations, roles, permissions, redirect/CORS/webhook hints, JWT/OIDC
|
|
14
|
+
claim mapping, and permission-derived Forge policies.
|
|
15
|
+
- Added `forge authmd generate` and `forge authmd check`, including
|
|
16
|
+
`public/auth.md`, OAuth protected-resource metadata, command/policy/tenant
|
|
17
|
+
requirements, approval metadata, and `forge dev` serving for `/auth.md` and
|
|
18
|
+
`/.well-known/oauth-protected-resource`.
|
|
19
|
+
- Added local WorkOS/FGA scaffolding without requiring real WorkOS credentials:
|
|
20
|
+
resource graph helpers, cross-tenant guards, FGA check cache/fallback
|
|
21
|
+
telemetry, a mock WorkOS testkit, and Acme/Globex multi-tenant regression
|
|
22
|
+
coverage.
|
|
23
|
+
- Taught Forge auth and policies to evaluate permission claims alongside
|
|
24
|
+
roles, including dev-header permission simulation.
|
|
25
|
+
- Added `forge version --json` as a command alias and improved the generated
|
|
26
|
+
agent contract/capability map so table reads performed by imported local
|
|
27
|
+
helpers are captured.
|
|
28
|
+
|
|
29
|
+
## 0.1.0-alpha.28
|
|
30
|
+
|
|
31
|
+
- Accepted visible Codex hook canaries as sufficient for local editing while
|
|
32
|
+
reporting native Codex provenance separately through `nativeTrustStatus`, so
|
|
33
|
+
`waiting-for-user-trust` no longer implies missing user approval when the
|
|
34
|
+
canary path is already working.
|
|
35
|
+
- Added regression coverage and docs for the split between hook approval,
|
|
36
|
+
canary readiness, and trusted native Codex signal proof.
|
|
37
|
+
|
|
9
38
|
## 0.1.0-alpha.27
|
|
10
39
|
|
|
11
40
|
- Stabilized the `forge add convex` loop: integration docs/testkits are now
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.
|
|
1
|
+
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.29","releaseId":"forgeos@0.1.0-alpha.29+unknown","schemaVersion":"0.1.0"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.29 input=25d84ce77a02f5c4ce49fa082fa968608c9859594d5876886344276ca0c27523 content=ace6a64b0efd317e17fff6fb883bd20137bde066cbee7c2a304be18b90a20fd0
|
|
2
2
|
export const releaseManifest = {
|
|
3
3
|
"defaultProvider": "local",
|
|
4
4
|
"diagnostics": [],
|
|
@@ -19,7 +19,7 @@ export const releaseManifest = {
|
|
|
19
19
|
"custom"
|
|
20
20
|
],
|
|
21
21
|
"packageName": "forgeos",
|
|
22
|
-
"packageVersion": "0.1.0-alpha.
|
|
23
|
-
"releaseId": "forgeos@0.1.0-alpha.
|
|
22
|
+
"packageVersion": "0.1.0-alpha.29",
|
|
23
|
+
"releaseId": "forgeos@0.1.0-alpha.29+unknown",
|
|
24
24
|
"schemaVersion": "0.1.0"
|
|
25
25
|
} as const;
|
|
@@ -987,27 +987,50 @@ function hookApprovalStatusFor(
|
|
|
987
987
|
target: AgentAdapterTarget,
|
|
988
988
|
installed: boolean,
|
|
989
989
|
nativeSignals: number,
|
|
990
|
+
observableSignals = 0,
|
|
990
991
|
memoryReadable = true,
|
|
991
|
-
): "not-required" | "waiting-for-user-trust" | "trusted" | "memory-unavailable" {
|
|
992
|
+
): "not-required" | "waiting-for-user-trust" | "accepted" | "trusted" | "memory-unavailable" {
|
|
992
993
|
if (target !== "codex" || !installed) {
|
|
993
994
|
return "not-required";
|
|
994
995
|
}
|
|
995
996
|
if (!memoryReadable) {
|
|
996
997
|
return "memory-unavailable";
|
|
997
998
|
}
|
|
998
|
-
|
|
999
|
+
if (nativeSignals > 0) {
|
|
1000
|
+
return "trusted";
|
|
1001
|
+
}
|
|
1002
|
+
return observableSignals > 0 ? "accepted" : "waiting-for-user-trust";
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function hookNativeTrustStatusFor(
|
|
1006
|
+
target: AgentAdapterTarget,
|
|
1007
|
+
installed: boolean,
|
|
1008
|
+
nativeSignals: number,
|
|
1009
|
+
memoryReadable = true,
|
|
1010
|
+
): "not-required" | "waiting-for-native-signal" | "trusted" | "memory-unavailable" {
|
|
1011
|
+
if (target !== "codex" || !installed) {
|
|
1012
|
+
return "not-required";
|
|
1013
|
+
}
|
|
1014
|
+
if (!memoryReadable) {
|
|
1015
|
+
return "memory-unavailable";
|
|
1016
|
+
}
|
|
1017
|
+
return nativeSignals > 0 ? "trusted" : "waiting-for-native-signal";
|
|
999
1018
|
}
|
|
1000
1019
|
|
|
1001
1020
|
function codexHookApprovalMessage(
|
|
1002
1021
|
approvalStatus: ReturnType<typeof hookApprovalStatusFor>,
|
|
1022
|
+
nativeTrustStatus: ReturnType<typeof hookNativeTrustStatusFor>,
|
|
1003
1023
|
canarySignals = 0,
|
|
1004
1024
|
): string {
|
|
1005
1025
|
if (approvalStatus === "waiting-for-user-trust") {
|
|
1006
1026
|
if (canarySignals > 0) {
|
|
1007
|
-
return "ForgeOS can see the Codex smoke canary
|
|
1027
|
+
return "ForgeOS can see the Codex smoke canary; hook approval is accepted, but trusted native Codex signal proof is still pending";
|
|
1008
1028
|
}
|
|
1009
1029
|
return "ForgeOS has not seen a trusted native Codex hook signal yet; approve the Codex Desktop hook prompt if shown, then continue a Codex session in this workspace";
|
|
1010
1030
|
}
|
|
1031
|
+
if (approvalStatus === "accepted" && nativeTrustStatus === "waiting-for-native-signal") {
|
|
1032
|
+
return "Codex hook approval is accepted for local editing; ForgeOS is still waiting for a trusted native Codex signal for stronger provenance";
|
|
1033
|
+
}
|
|
1011
1034
|
if (approvalStatus === "trusted") {
|
|
1012
1035
|
return "Codex Desktop hook trust is confirmed by a native hook signal";
|
|
1013
1036
|
}
|
|
@@ -1286,6 +1309,7 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
|
|
|
1286
1309
|
canarySignals: 0,
|
|
1287
1310
|
approvalRequired: false,
|
|
1288
1311
|
approvalStatus: "not-required",
|
|
1312
|
+
nativeTrustStatus: "not-required",
|
|
1289
1313
|
checks: [{ name: "target", ok: false, message: diag.message }],
|
|
1290
1314
|
nextActions: ["forge agent list-targets --json"],
|
|
1291
1315
|
diagnostics: [diag],
|
|
@@ -1321,9 +1345,10 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
|
|
|
1321
1345
|
const nativeSignals = nativeEvents.length + (queuedHooks?.nativeSignals ?? 0);
|
|
1322
1346
|
const canarySignals = canaryEvents.length + (queuedHooks?.canarySignals ?? 0);
|
|
1323
1347
|
const visibleHookSignals = visibleInMemory || queuedEvents > 0;
|
|
1324
|
-
const
|
|
1348
|
+
const observableSignals = Math.max(usefulSignals, canarySignals);
|
|
1349
|
+
const approvalStatus = hookApprovalStatusFor(target, installed, nativeSignals, observableSignals, deltaWritable);
|
|
1350
|
+
const nativeTrustStatus = hookNativeTrustStatusFor(target, installed, nativeSignals, deltaWritable);
|
|
1325
1351
|
const approvalRequired = approvalStatus === "waiting-for-user-trust";
|
|
1326
|
-
const trustedHookSignals = target === "codex" ? nativeSignals > 0 : true;
|
|
1327
1352
|
const codexHookInspection = installTarget === "codex"
|
|
1328
1353
|
? inspectCodexHookCommands(options.workspaceRoot)
|
|
1329
1354
|
: undefined;
|
|
@@ -1340,7 +1365,7 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
|
|
|
1340
1365
|
const usesLightweightRunner = codexHookInspection?.usesLightweightRunner === true;
|
|
1341
1366
|
const hookCommandHealthy = installTarget !== "codex" || (usesLightweightRunner && !usesLegacyForgeCli);
|
|
1342
1367
|
const hookVersionHealthy = installTarget !== "codex" || !usesLightweightRunner || codexVersionMatch?.matches === true;
|
|
1343
|
-
const ok = installed && bridgeWritable && deltaWritable && visibleHookSignals && usefulSignals > 0 &&
|
|
1368
|
+
const ok = installed && bridgeWritable && deltaWritable && visibleHookSignals && usefulSignals > 0 && !approvalRequired && hookCommandHealthy && hookVersionHealthy;
|
|
1344
1369
|
const nextActions = ok
|
|
1345
1370
|
? uniqueCommands([
|
|
1346
1371
|
...(queuedEvents > 0 ? [`forge agent ingest ${source} --file .forge/agent/events.ndjson --json`] : []),
|
|
@@ -1374,6 +1399,7 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
|
|
|
1374
1399
|
canarySignals,
|
|
1375
1400
|
approvalRequired,
|
|
1376
1401
|
approvalStatus,
|
|
1402
|
+
nativeTrustStatus,
|
|
1377
1403
|
workspaceRoot: memory.workspaceRoot,
|
|
1378
1404
|
ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
|
|
1379
1405
|
...(lastAgentSignal(memory.events) ? { lastSignal: lastAgentSignal(memory.events) } : {}),
|
|
@@ -1435,28 +1461,29 @@ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentH
|
|
|
1435
1461
|
},
|
|
1436
1462
|
{
|
|
1437
1463
|
name: "native-hook-signal",
|
|
1438
|
-
ok: target !== "codex" || !deltaWritable || nativeSignals > 0,
|
|
1464
|
+
ok: target !== "codex" || !deltaWritable || nativeSignals > 0 || approvalStatus === "accepted",
|
|
1439
1465
|
message: target !== "codex"
|
|
1440
1466
|
? "native Codex hook approval is not required for this target"
|
|
1441
1467
|
: !deltaWritable
|
|
1442
1468
|
? "trusted Codex native hook signals cannot be verified until Agent Memory is readable"
|
|
1443
1469
|
: nativeSignals > 0
|
|
1444
1470
|
? `${nativeSignals} trusted Codex native hook signal(s) visible or queued`
|
|
1445
|
-
: "Codex has not emitted a trusted native hook signal yet",
|
|
1471
|
+
: "hook approval is accepted, but Codex has not emitted a trusted native hook signal yet",
|
|
1446
1472
|
evidence: {
|
|
1447
1473
|
nativeSignals,
|
|
1448
1474
|
canarySignals,
|
|
1449
1475
|
queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
|
|
1450
1476
|
queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
|
|
1451
1477
|
memoryReadable: deltaWritable,
|
|
1478
|
+
nativeTrustStatus,
|
|
1452
1479
|
trustBoundary: target === "codex" ? "codex-desktop-hook-approval" : "not-required",
|
|
1453
1480
|
},
|
|
1454
1481
|
},
|
|
1455
1482
|
{
|
|
1456
1483
|
name: "codex-hook-approval",
|
|
1457
1484
|
ok: !approvalRequired && approvalStatus !== "memory-unavailable",
|
|
1458
|
-
message: codexHookApprovalMessage(approvalStatus, canarySignals),
|
|
1459
|
-
evidence: { approvalStatus, approvalRequired },
|
|
1485
|
+
message: codexHookApprovalMessage(approvalStatus, nativeTrustStatus, canarySignals),
|
|
1486
|
+
evidence: { approvalStatus, approvalRequired, nativeTrustStatus },
|
|
1460
1487
|
},
|
|
1461
1488
|
...(installTarget === "codex"
|
|
1462
1489
|
? [
|
|
@@ -1556,7 +1583,15 @@ export async function runAgentDoctor(options: AgentCommandOptions): Promise<Agen
|
|
|
1556
1583
|
const adapterState = check.missing.length > 0 ? "missing" : check.stale.length > 0 ? "stale" : "ready";
|
|
1557
1584
|
const installed = !installTarget || hookFiles.missing.length === 0;
|
|
1558
1585
|
const memoryReadable = memoryDiagnostics.length === 0;
|
|
1559
|
-
const
|
|
1586
|
+
const observableSignals = Math.max(usefulSignals, canarySignals);
|
|
1587
|
+
const approvalStatus = hookApprovalStatusFor(
|
|
1588
|
+
target,
|
|
1589
|
+
Boolean(installTarget && installed),
|
|
1590
|
+
nativeSignals,
|
|
1591
|
+
observableSignals,
|
|
1592
|
+
memoryReadable,
|
|
1593
|
+
);
|
|
1594
|
+
const nativeTrustStatus = hookNativeTrustStatusFor(target, Boolean(installTarget && installed), nativeSignals, memoryReadable);
|
|
1560
1595
|
const approvalRequired = approvalStatus === "waiting-for-user-trust";
|
|
1561
1596
|
const hookBridgeState = !installTarget
|
|
1562
1597
|
? "not-supported"
|
|
@@ -1635,28 +1670,29 @@ export async function runAgentDoctor(options: AgentCommandOptions): Promise<Agen
|
|
|
1635
1670
|
},
|
|
1636
1671
|
{
|
|
1637
1672
|
name: "native-hook-signal",
|
|
1638
|
-
ok: !installTarget || target !== "codex" || !memoryReadable || nativeSignals > 0,
|
|
1673
|
+
ok: !installTarget || target !== "codex" || !memoryReadable || nativeSignals > 0 || approvalStatus === "accepted",
|
|
1639
1674
|
message: !installTarget || target !== "codex"
|
|
1640
1675
|
? "native Codex hook approval is not required for this target"
|
|
1641
1676
|
: !memoryReadable
|
|
1642
1677
|
? "trusted Codex native hook signals cannot be verified until Agent Memory is readable"
|
|
1643
1678
|
: nativeSignals > 0
|
|
1644
1679
|
? `${nativeSignals} trusted Codex native hook signal(s) visible or queued`
|
|
1645
|
-
: "Codex has not emitted a trusted native hook signal yet",
|
|
1680
|
+
: "hook approval is accepted, but Codex has not emitted a trusted native hook signal yet",
|
|
1646
1681
|
evidence: {
|
|
1647
1682
|
nativeSignals,
|
|
1648
1683
|
canarySignals,
|
|
1649
1684
|
queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
|
|
1650
1685
|
queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
|
|
1651
1686
|
memoryReadable,
|
|
1687
|
+
nativeTrustStatus,
|
|
1652
1688
|
trustBoundary: target === "codex" ? "codex-desktop-hook-approval" : "not-required",
|
|
1653
1689
|
},
|
|
1654
1690
|
},
|
|
1655
1691
|
{
|
|
1656
1692
|
name: "codex-hook-approval",
|
|
1657
1693
|
ok: !approvalRequired && approvalStatus !== "memory-unavailable",
|
|
1658
|
-
message: codexHookApprovalMessage(approvalStatus, canarySignals),
|
|
1659
|
-
evidence: { approvalStatus, approvalRequired },
|
|
1694
|
+
message: codexHookApprovalMessage(approvalStatus, nativeTrustStatus, canarySignals),
|
|
1695
|
+
evidence: { approvalStatus, approvalRequired, nativeTrustStatus },
|
|
1660
1696
|
},
|
|
1661
1697
|
{ name: "secret-scan", ok: !check.diagnostics.some((diag) => diag.code === FORGE_AGENT_SECRET_LEAK) },
|
|
1662
1698
|
];
|
|
@@ -1683,6 +1719,7 @@ export async function runAgentDoctor(options: AgentCommandOptions): Promise<Agen
|
|
|
1683
1719
|
hookBridge: hookBridgeState,
|
|
1684
1720
|
approvalRequired,
|
|
1685
1721
|
approvalStatus,
|
|
1722
|
+
nativeTrustStatus,
|
|
1686
1723
|
recentEvents: recentEvents.length,
|
|
1687
1724
|
queuedEvents,
|
|
1688
1725
|
usefulSignals,
|
|
@@ -1832,7 +1869,7 @@ export async function runAgentOnboard(options: AgentCommandOptions): Promise<Age
|
|
|
1832
1869
|
name: "hook-smoke",
|
|
1833
1870
|
ok: hookSmoke.ok,
|
|
1834
1871
|
message: hookSmoke.approvalRequired
|
|
1835
|
-
? `${target} hook files
|
|
1872
|
+
? `${target} hook files are installed, but no accepted hook signal is visible yet`
|
|
1836
1873
|
: hookSmoke.ok
|
|
1837
1874
|
? `${target} hooks recorded a useful canary signal`
|
|
1838
1875
|
: `${target} hooks did not prove memory visibility`,
|
|
@@ -1854,6 +1891,8 @@ export async function runAgentOnboard(options: AgentCommandOptions): Promise<Age
|
|
|
1854
1891
|
? "Codex hook trust cannot be verified until Agent Memory is readable"
|
|
1855
1892
|
: doctor.summary.approvalStatus === "trusted"
|
|
1856
1893
|
? "Codex hook trust is confirmed by a native hook signal"
|
|
1894
|
+
: doctor.summary.approvalStatus === "accepted"
|
|
1895
|
+
? "Codex hook approval is accepted; trusted native signal proof is still pending"
|
|
1857
1896
|
: "hook approval is not required for this target",
|
|
1858
1897
|
}]
|
|
1859
1898
|
: []),
|
|
@@ -1899,6 +1938,7 @@ export async function runAgentOnboard(options: AgentCommandOptions): Promise<Age
|
|
|
1899
1938
|
hookBridge: doctor.summary.hookBridge,
|
|
1900
1939
|
approvalRequired: doctor.summary.approvalRequired,
|
|
1901
1940
|
approvalStatus: doctor.summary.approvalStatus,
|
|
1941
|
+
nativeTrustStatus: doctor.summary.nativeTrustStatus,
|
|
1902
1942
|
memorySignals: doctor.summary.usefulSignals,
|
|
1903
1943
|
nativeSignals: doctor.summary.nativeSignals,
|
|
1904
1944
|
canarySignals: doctor.summary.canarySignals,
|
|
@@ -1956,6 +1996,7 @@ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<
|
|
|
1956
1996
|
canarySignals: 0,
|
|
1957
1997
|
approvalRequired: false,
|
|
1958
1998
|
approvalStatus: "not-required",
|
|
1999
|
+
nativeTrustStatus: "not-required",
|
|
1959
2000
|
checks: [{ name: "target", ok: false, message: diag.message }],
|
|
1960
2001
|
nextActions: ["forge agent list-targets --json"],
|
|
1961
2002
|
diagnostics: [diag],
|
|
@@ -2060,7 +2101,7 @@ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<
|
|
|
2060
2101
|
{
|
|
2061
2102
|
name: "codex-hook-approval",
|
|
2062
2103
|
ok: status.approvalStatus !== "memory-unavailable",
|
|
2063
|
-
message: codexHookApprovalMessage(status.approvalStatus, status.canarySignals),
|
|
2104
|
+
message: codexHookApprovalMessage(status.approvalStatus, status.nativeTrustStatus, status.canarySignals),
|
|
2064
2105
|
},
|
|
2065
2106
|
...(installTarget === "codex"
|
|
2066
2107
|
? [
|
|
@@ -2110,7 +2151,7 @@ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<
|
|
|
2110
2151
|
? [createDiagnostic({
|
|
2111
2152
|
severity: "warning",
|
|
2112
2153
|
code: "FORGE_AGENT_HOOK_APPROVAL_REQUIRED",
|
|
2113
|
-
message: "Codex Desktop
|
|
2154
|
+
message: "Codex Desktop hook approval is still pending because ForgeOS has not seen a canary, useful hook event, or trusted native hook signal yet. Approve the hook prompt if Codex shows one, then continue a Codex session in this workspace.",
|
|
2114
2155
|
suggestedCommands: hookApprovalNextActions(target, status.canarySignals),
|
|
2115
2156
|
})]
|
|
2116
2157
|
: []),
|
|
@@ -2147,6 +2188,7 @@ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<
|
|
|
2147
2188
|
canarySignals: status.canarySignals,
|
|
2148
2189
|
approvalRequired: status.approvalRequired,
|
|
2149
2190
|
approvalStatus: status.approvalStatus,
|
|
2191
|
+
nativeTrustStatus: status.nativeTrustStatus,
|
|
2150
2192
|
...(canaryEvent ? { lastSignal: lastAgentSignal([canaryEvent]) } : status.lastSignal ? { lastSignal: status.lastSignal } : {}),
|
|
2151
2193
|
...(hookRunnerProbe ? { hookRunnerProbe } : {}),
|
|
2152
2194
|
canary: {
|
|
@@ -2309,6 +2351,7 @@ export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentComma
|
|
|
2309
2351
|
`ready to edit: ${result.readyToEdit ? "yes" : "no"}`,
|
|
2310
2352
|
`hook bridge: ${result.summary.hookBridge}`,
|
|
2311
2353
|
`hook approval: ${result.summary.approvalStatus}`,
|
|
2354
|
+
`native trust: ${result.summary.nativeTrustStatus}`,
|
|
2312
2355
|
`generated fresh: ${result.summary.generatedFresh ? "yes" : "no"}`,
|
|
2313
2356
|
`generated changed: ${result.summary.generatedChangedFiles}`,
|
|
2314
2357
|
`changed files: ${result.summary.changedFiles}`,
|
|
@@ -2326,6 +2369,7 @@ export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentComma
|
|
|
2326
2369
|
`trusted native ready: ${smoke.trustedNativeReady ? "yes" : "no"}`,
|
|
2327
2370
|
`readiness level: ${smoke.readinessLevel}`,
|
|
2328
2371
|
`approval: ${smoke.approvalStatus}`,
|
|
2372
|
+
`native trust: ${smoke.nativeTrustStatus}`,
|
|
2329
2373
|
`native signals: ${smoke.nativeSignals}`,
|
|
2330
2374
|
`canary signals: ${smoke.canarySignals}`,
|
|
2331
2375
|
...smoke.checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.name}${check.message ? `: ${check.message}` : ""}`),
|
|
@@ -2362,6 +2406,7 @@ export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentComma
|
|
|
2362
2406
|
`native signals: ${result.nativeSignals}`,
|
|
2363
2407
|
`canary signals: ${result.canarySignals}`,
|
|
2364
2408
|
`approval: ${result.approvalStatus}`,
|
|
2409
|
+
`native trust: ${result.nativeTrustStatus}`,
|
|
2365
2410
|
...("recentEvents" in result ? [`recent events: ${result.recentEvents}`] : []),
|
|
2366
2411
|
...("queuedEvents" in result ? [`queued events: ${result.queuedEvents ?? 0}`] : []),
|
|
2367
2412
|
...(result.lastSignal ? [`last signal: ${result.lastSignal.kind}${result.lastSignal.summary ? ` - ${result.lastSignal.summary}` : ""}`] : []),
|
|
@@ -140,7 +140,8 @@ export interface AgentPrintContextResult {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
export type AgentHookBridgeState = "ready" | "missing" | "not-supported" | "waiting-for-user-trust" | "memory-unavailable";
|
|
143
|
-
export type AgentHookApprovalStatus = "not-required" | "waiting-for-user-trust" | "trusted" | "memory-unavailable";
|
|
143
|
+
export type AgentHookApprovalStatus = "not-required" | "waiting-for-user-trust" | "accepted" | "trusted" | "memory-unavailable";
|
|
144
|
+
export type AgentHookNativeTrustStatus = "not-required" | "waiting-for-native-signal" | "trusted" | "memory-unavailable";
|
|
144
145
|
export type AgentHookReadinessLevel = "none" | "canary" | "trusted-native";
|
|
145
146
|
|
|
146
147
|
export interface AgentDoctorResult {
|
|
@@ -151,6 +152,7 @@ export interface AgentDoctorResult {
|
|
|
151
152
|
hookBridge: AgentHookBridgeState;
|
|
152
153
|
approvalRequired: boolean;
|
|
153
154
|
approvalStatus: AgentHookApprovalStatus;
|
|
155
|
+
nativeTrustStatus: AgentHookNativeTrustStatus;
|
|
154
156
|
recentEvents: number;
|
|
155
157
|
queuedEvents?: number;
|
|
156
158
|
usefulSignals: number;
|
|
@@ -193,6 +195,7 @@ export interface AgentOnboardResult {
|
|
|
193
195
|
hookBridge: AgentHookBridgeState;
|
|
194
196
|
approvalRequired: boolean;
|
|
195
197
|
approvalStatus: AgentHookApprovalStatus;
|
|
198
|
+
nativeTrustStatus: AgentHookNativeTrustStatus;
|
|
196
199
|
memorySignals: number;
|
|
197
200
|
nativeSignals: number;
|
|
198
201
|
canarySignals: number;
|
|
@@ -239,6 +242,7 @@ export interface AgentHooksSmokeResult {
|
|
|
239
242
|
canarySignals: number;
|
|
240
243
|
approvalRequired: boolean;
|
|
241
244
|
approvalStatus: AgentHookApprovalStatus;
|
|
245
|
+
nativeTrustStatus: AgentHookNativeTrustStatus;
|
|
242
246
|
lastSignal?: {
|
|
243
247
|
kind: string;
|
|
244
248
|
summary?: string;
|
|
@@ -285,6 +289,7 @@ export interface AgentHooksStatusResult {
|
|
|
285
289
|
canarySignals: number;
|
|
286
290
|
approvalRequired: boolean;
|
|
287
291
|
approvalStatus: AgentHookApprovalStatus;
|
|
292
|
+
nativeTrustStatus: AgentHookNativeTrustStatus;
|
|
288
293
|
lastSignal?: {
|
|
289
294
|
kind: string;
|
|
290
295
|
summary?: string;
|
package/src/forge/cli/auth.ts
CHANGED
|
@@ -4,10 +4,11 @@ import {
|
|
|
4
4
|
FORGE_AUTH_INVALID_AUDIENCE,
|
|
5
5
|
FORGE_AUTH_JWKS_FAILED,
|
|
6
6
|
} from "../compiler/diagnostics/codes.ts";
|
|
7
|
-
import { loadAuthConfigFromEnv } from "../runtime/auth/config.ts";
|
|
7
|
+
import { loadAuthConfigFromEnv, type AuthClaimsMapping } from "../runtime/auth/config.ts";
|
|
8
8
|
import { mapClaimsToAuthContext } from "../runtime/auth/claims.ts";
|
|
9
9
|
import { ForgeAuthError } from "../runtime/auth/errors.ts";
|
|
10
10
|
import { verifyJwtToken } from "../runtime/auth/verifier.ts";
|
|
11
|
+
import { loadSecretRegistry } from "../runtime/secrets/check.ts";
|
|
11
12
|
|
|
12
13
|
export type AuthSubcommand = "check" | "config" | "decode" | "test-token" | "jwks" | "prove";
|
|
13
14
|
|
|
@@ -26,9 +27,42 @@ export interface AuthCommandResult {
|
|
|
26
27
|
exitCode: 0 | 1;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
function detectWorkOS(workspaceRoot: string, claims: AuthClaimsMapping) {
|
|
31
|
+
const secretRegistry = loadSecretRegistry(workspaceRoot);
|
|
32
|
+
const secretNames = new Set((secretRegistry?.secrets ?? []).map((secret) => secret.name));
|
|
33
|
+
const detected =
|
|
34
|
+
secretNames.has("WORKOS_API_KEY") ||
|
|
35
|
+
secretNames.has("WORKOS_CLIENT_ID") ||
|
|
36
|
+
claims.tenantId === "organization_id";
|
|
37
|
+
const expectedClaims = {
|
|
38
|
+
userId: "sub",
|
|
39
|
+
email: "email",
|
|
40
|
+
tenantId: "organization_id",
|
|
41
|
+
role: "role",
|
|
42
|
+
roles: "roles",
|
|
43
|
+
permissions: "permissions",
|
|
44
|
+
};
|
|
45
|
+
const claimStatus = Object.entries(expectedClaims).map(([name, expected]) => ({
|
|
46
|
+
name,
|
|
47
|
+
expected,
|
|
48
|
+
actual: claims[name as keyof AuthClaimsMapping],
|
|
49
|
+
ok: claims[name as keyof AuthClaimsMapping] === expected,
|
|
50
|
+
}));
|
|
51
|
+
return {
|
|
52
|
+
detected,
|
|
53
|
+
requiredSecretsRegistered: ["WORKOS_API_KEY", "WORKOS_CLIENT_ID", "WORKOS_COOKIE_PASSWORD"].every((name) =>
|
|
54
|
+
secretNames.has(name)
|
|
55
|
+
),
|
|
56
|
+
webhookSecretRegistered: secretNames.has("WORKOS_WEBHOOK_SECRET"),
|
|
57
|
+
expectedClaims,
|
|
58
|
+
claimStatus,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
29
62
|
function validateConfig(workspaceRoot: string): AuthCommandResult {
|
|
30
63
|
const config = loadAuthConfigFromEnv(workspaceRoot);
|
|
31
64
|
const errors: { code: string; message: string }[] = [];
|
|
65
|
+
const workos = detectWorkOS(workspaceRoot, config.claims);
|
|
32
66
|
|
|
33
67
|
if ((config.mode === "jwt" || config.mode === "oidc") && !config.issuer) {
|
|
34
68
|
errors.push({
|
|
@@ -60,6 +94,7 @@ function validateConfig(workspaceRoot: string): AuthCommandResult {
|
|
|
60
94
|
algorithms: config.algorithms,
|
|
61
95
|
claims: config.claims,
|
|
62
96
|
requiresTenant: config.requiresTenant,
|
|
97
|
+
workos,
|
|
63
98
|
errors,
|
|
64
99
|
},
|
|
65
100
|
error: errors[0],
|
|
@@ -69,6 +104,7 @@ function validateConfig(workspaceRoot: string): AuthCommandResult {
|
|
|
69
104
|
|
|
70
105
|
function publicConfig(workspaceRoot: string): AuthCommandResult {
|
|
71
106
|
const config = loadAuthConfigFromEnv(workspaceRoot);
|
|
107
|
+
const workos = detectWorkOS(workspaceRoot, config.claims);
|
|
72
108
|
return {
|
|
73
109
|
ok: true,
|
|
74
110
|
mode: config.mode,
|
|
@@ -80,6 +116,7 @@ function publicConfig(workspaceRoot: string): AuthCommandResult {
|
|
|
80
116
|
algorithms: config.algorithms,
|
|
81
117
|
claims: config.claims,
|
|
82
118
|
requiresTenant: config.requiresTenant,
|
|
119
|
+
workos,
|
|
83
120
|
},
|
|
84
121
|
exitCode: 0,
|
|
85
122
|
};
|
|
@@ -166,7 +203,9 @@ export async function runAuthCommand(
|
|
|
166
203
|
if (options.subcommand === "prove") {
|
|
167
204
|
const checked = validateConfig(options.workspaceRoot);
|
|
168
205
|
const config = loadAuthConfigFromEnv(options.workspaceRoot);
|
|
206
|
+
const workos = detectWorkOS(options.workspaceRoot, config.claims);
|
|
169
207
|
const productionMode = config.mode === "jwt" || config.mode === "oidc";
|
|
208
|
+
const workosClaimsOk = workos.claimStatus.every((claim) => claim.ok);
|
|
170
209
|
return {
|
|
171
210
|
ok: checked.ok,
|
|
172
211
|
mode: config.mode,
|
|
@@ -191,7 +230,23 @@ export async function runAuthCommand(
|
|
|
191
230
|
status: checked.ok ? "passed" : "failed",
|
|
192
231
|
evidence: checked.data,
|
|
193
232
|
},
|
|
233
|
+
{
|
|
234
|
+
id: "INV-WORKOS-001",
|
|
235
|
+
name: "WorkOS adapter claim mapping is explicit when WorkOS is present",
|
|
236
|
+
status: !workos.detected ? "not-applicable" : workosClaimsOk ? "passed" : "failed",
|
|
237
|
+
evidence: workos.claimStatus,
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "INV-WORKOS-002",
|
|
241
|
+
name: "WorkOS required secret names are registered without values",
|
|
242
|
+
status: !workos.detected ? "not-applicable" : workos.requiredSecretsRegistered ? "passed" : "failed",
|
|
243
|
+
evidence: {
|
|
244
|
+
required: ["WORKOS_API_KEY", "WORKOS_CLIENT_ID", "WORKOS_COOKIE_PASSWORD"],
|
|
245
|
+
webhookSecretRegistered: workos.webhookSecretRegistered,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
194
248
|
],
|
|
249
|
+
workos,
|
|
195
250
|
checkedAt: "deterministic",
|
|
196
251
|
},
|
|
197
252
|
error: checked.error,
|