@agenticprimitives/contracts 1.0.0-alpha.14 → 1.0.0-alpha.16

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.
@@ -1,37 +1,37 @@
1
1
  {
2
- "agentAccountFactory": "0xfCFafCbA6D88351aAAA190a5E971F06CAb40437d",
3
- "agentAccountImplementation": "0x28b7d8e4682EF36bE479330Ba6CFE78E9E724Bc4",
4
- "agentNameRegistry": "0x419928546Be67ba83C2aEaFCb6AD3364D5659C8D",
5
- "agentNameResolver": "0xf96A25DeBF92C514459DC904ec8b79CE52D265A5",
6
- "agentNameUniversalResolver": "0x3aC8Cb8Df1A8F9fcA05a37cEC82426b4432A53D3",
7
- "agentProfileResolver": "0xb988e75179e11E4d17665d61c6a9Ce0e737A808F",
8
- "agentRegistryBase": "0x7fab087fc3d0e1793bdb1d4c5d253635131ee1d1",
9
- "agentRelationship": "0xDc409D69aC002B4FC90D4B453cc6B6277B98CB46",
10
- "agreementRegistry": "0x882371Dc898f4835335a01558aE5C2B306069FcF",
11
- "allowedMethodsEnforcer": "0x1Df87222f53ad26F599C60364F7232a3c0393ad9",
12
- "allowedTargetsEnforcer": "0x55A9fD6aF8688EE9c68D15CDAa84d9DBD027B80F",
13
- "approvedHashRegistry": "0x878272cc4E0c5bA026CEc29C9722F41fC0515fE5",
14
- "attestationRegistry": "0xC695939EA5fB1d7a88BCF536EAC3C13bfcc9E50d",
15
- "callDataHashEnforcer": "0x4ab106b80488FE6A920D973cDb96052a933c7450",
2
+ "agentAccountFactory": "0xE5d2d68b510cC2325fAB5e278855F10DcF02B80A",
3
+ "agentAccountImplementation": "0x3C547Ad14CcB3af20b86D671B0B04E61294ad05F",
4
+ "agentNameRegistry": "0xD9a1eCe15F4265c2f367ccD7Df740baeDc3d0523",
5
+ "agentNameResolver": "0x6E749688a49C3e32B59A54d374D2C116c4Bcc2ea",
6
+ "agentNameUniversalResolver": "0xCbb8C3E3a0C87962dC4831152c936Ca2A8B7826b",
7
+ "agentProfileResolver": "0x821de228dF591089575D9186BF9b5E305f12A221",
8
+ "agentRelationship": "0x1c6D59DD6fd9C7AF39B5afD06FFE7FC032E3608e",
9
+ "agreementRegistry": "0x92c4C46619842A8DDC5F59191d5FDb2c0aFc6874",
10
+ "allowedMethodsEnforcer": "0xb6fa9a218F4d6d3Af3dDaE26E3639a98632d4C09",
11
+ "allowedTargetsEnforcer": "0xca53c91257f6Fa2d1Bc51411B8da40056c861965",
12
+ "approvedHashRegistry": "0x97e322FCDc233Ef4f8f81979D6e12bCdEF226eF0",
13
+ "attestationRegistry": "0x34eF353F7DCdad386b7DD8643248A00bFcE863b8",
14
+ "callDataHashEnforcer": "0x5d8e57091d716A3623c29Bd6CD07A502484dA845",
16
15
  "chainId": 84532,
17
- "custodyPolicy": "0xfAFDc79bc03CC7cE27f7AA12A3180E4E4C0BC3F6",
18
- "delegationManager": "0x10DB7c0C5887910722354ef37eF874fBD124026C",
16
+ "custodyPolicy": "0x7d94B5DAc5d66d30e9D2Ed401BbAB237e86804E6",
17
+ "delegationManager": "0xd494a868B3216A307e9eCAe542D3903e3e364377",
19
18
  "deployer": "0x31ed17fb99e82E02085Ab4B3cbdaB05489098b44",
20
- "entryPoint": "0x86Dc419feDD30F58250B314F5947aDd8cf379436",
21
- "geoFeatureRegistry": "0x1C3b16aF3C2649f2b1ac78aFD6AB20dA0c379121",
22
- "mockUsdc": "0x027de10c034f4055551C32491BCe65711BFE5f69",
23
- "ontologyTermRegistry": "0x95C149Be91c925FB348Fa231aD28440FBaD562cF",
24
- "paymentEnforcer": "0xB09b95E7BcB5d1b3C934facFBEd5bc474b15897E",
25
- "paymentEscrow": "0x053dA1bdD4f0A75b82A75A46F189f137a5267928",
26
- "paymentReceiptRegistry": "0x29645211A44714b06B8c4C55c109Ef285699f56c",
27
- "permissionlessSubregistry": "0x542915A4118A28f49D40DAE53Ee0dc5721Aa4868",
28
- "permissionlessSubregistryDemoAgent": "0x00EB165eA2fd33448bbBd5634a8ba92D616AEc1D",
29
- "quorumEnforcer": "0x093ff9790601F712B3730C3cAa34f9353dC93739",
30
- "relationshipTypeRegistry": "0x0250274222baBB42d44e72e9992DFEB701399a56",
31
- "shapeRegistry": "0x7d0a4e9A981775a3807282bbf3499e77f5E2bA53",
32
- "skillDefinitionRegistry": "0x7be2c5615d83Ca95e02b9BDe24879eb220cb1780",
33
- "smartAgentPaymaster": "0xe93f4ea2cEA8CFF898948c54D6d6144A8F14ca72",
34
- "timestampEnforcer": "0x118A74d6F59ecD9D19BA4b720157a007b6f39894",
35
- "universalSignatureValidator": "0x3Bc1Debf2ac2a59548aC5Ad09404A56bA21b5803",
36
- "valueEnforcer": "0xB7952Ca4a5E10efD24C03c069a0Ea7e262ebE959"
19
+ "entryPoint": "0x9B766cc05BD2193B5bE5dE50DB23DdB5F0B79c00",
20
+ "geoFeatureRegistry": "0x903c289a091CC9200811202De7dF3C22B18325d6",
21
+ "mockUsdc": "0xac4bf60DC027F71e70e7A821df64549e46d72Ed8",
22
+ "ontologyTermRegistry": "0x701F8a8521Dfa814d042781C42fA5e88fbf88F86",
23
+ "paymentEnforcer": "0x4680bcFBB2c2DC40254365300b922659919e3508",
24
+ "paymentReceiptRegistry": "0x496f9B025BB3FBcE8CDB6375176dd30B2eCE5D73",
25
+ "permissionlessSubregistry": "0xB4eF2166CEAccF8b2bbeAC9d0e28f991eb012385",
26
+ "permissionlessSubregistryDemoAgent": "0x857Bec62Dc039A9D06602335d060886206fC0972",
27
+ "quorumEnforcer": "0x38ffeA6A7c332a841E5fb33ED71B0BBF3387aFE1",
28
+ "relationshipTypeRegistry": "0x6E6ec3d0fDA798c86D324F2681ad6E16a7435658",
29
+ "shapeRegistry": "0xe105C57D384a62a3D465Ad6Ca2401B7117bC3415",
30
+ "skillDefinitionRegistry": "0xA00622b30a1cCe201C27A7a9c9dAAe1349492457",
31
+ "smartAgentPaymaster": "0xf40792F5b4d56931B9f77b4ae356fA89eA526224",
32
+ "timestampEnforcer": "0x579e5cDDd94C27688CA5A0DDd2256995e7759280",
33
+ "universalSignatureValidator": "0x01EC03058e98D0E56509E63b20F8454Fc31681F2",
34
+ "valueEnforcer": "0x8cC88ac64F3A4C5784E7F8B41cAd8E17Ea05c34B",
35
+ "agentRegistryBase": "0x1f8609F11fcaf0666534579EABe8B39778DFEdaA",
36
+ "paymentEscrow": "0xF523F7a30b22F35B2Ee506392cfA16BD8FEEe411"
37
37
  }
@@ -590,6 +590,25 @@
590
590
  ],
591
591
  "stateMutability": "view"
592
592
  },
593
+ {
594
+ "type": "function",
595
+ "name": "isExternalCustodianOrPasskey",
596
+ "inputs": [
597
+ {
598
+ "name": "account",
599
+ "type": "address",
600
+ "internalType": "address"
601
+ }
602
+ ],
603
+ "outputs": [
604
+ {
605
+ "name": "",
606
+ "type": "bool",
607
+ "internalType": "bool"
608
+ }
609
+ ],
610
+ "stateMutability": "view"
611
+ },
593
612
  {
594
613
  "type": "function",
595
614
  "name": "isModuleInstalled",
@@ -448,6 +448,27 @@
448
448
  "name": "SystemPaused",
449
449
  "inputs": []
450
450
  },
451
+ {
452
+ "type": "error",
453
+ "name": "TimelockBelowFloor",
454
+ "inputs": [
455
+ {
456
+ "name": "tier",
457
+ "type": "uint8",
458
+ "internalType": "uint8"
459
+ },
460
+ {
461
+ "name": "provided",
462
+ "type": "uint32",
463
+ "internalType": "uint32"
464
+ },
465
+ {
466
+ "name": "floorSecs",
467
+ "type": "uint32",
468
+ "internalType": "uint32"
469
+ }
470
+ ]
471
+ },
451
472
  {
452
473
  "type": "error",
453
474
  "name": "TooManyInitialCustodians",
@@ -854,6 +854,17 @@
854
854
  "name": "EmptyOwnerSet",
855
855
  "inputs": []
856
856
  },
857
+ {
858
+ "type": "error",
859
+ "name": "InfeasibleOrgConfig",
860
+ "inputs": [
861
+ {
862
+ "name": "nSigners",
863
+ "type": "uint8",
864
+ "internalType": "uint8"
865
+ }
866
+ ]
867
+ },
857
868
  {
858
869
  "type": "error",
859
870
  "name": "InvalidCustodyAction",
@@ -622,6 +622,17 @@
622
622
  "name": "LegacyRevocationDisabled",
623
623
  "inputs": []
624
624
  },
625
+ {
626
+ "type": "error",
627
+ "name": "NoCodeEnforcer",
628
+ "inputs": [
629
+ {
630
+ "name": "enforcer",
631
+ "type": "address",
632
+ "internalType": "address"
633
+ }
634
+ ]
635
+ },
625
636
  {
626
637
  "type": "error",
627
638
  "name": "NotDelegatorOrDelegate",
@@ -8,7 +8,6 @@ export interface Deployments {
8
8
  readonly "agentNameResolver": Address;
9
9
  readonly "agentNameUniversalResolver": Address;
10
10
  readonly "agentProfileResolver": Address;
11
- readonly "agentRegistryBase": Address;
12
11
  readonly "agentRelationship": Address;
13
12
  readonly "agreementRegistry": Address;
14
13
  readonly "allowedMethodsEnforcer": Address;
@@ -24,7 +23,6 @@ export interface Deployments {
24
23
  readonly "mockUsdc": Address;
25
24
  readonly "ontologyTermRegistry": Address;
26
25
  readonly "paymentEnforcer": Address;
27
- readonly "paymentEscrow": Address;
28
26
  readonly "paymentReceiptRegistry": Address;
29
27
  readonly "permissionlessSubregistry": Address;
30
28
  readonly "permissionlessSubregistryDemoAgent": Address;
@@ -36,6 +34,8 @@ export interface Deployments {
36
34
  readonly "timestampEnforcer": Address;
37
35
  readonly "universalSignatureValidator": Address;
38
36
  readonly "valueEnforcer": Address;
37
+ readonly "agentRegistryBase": Address;
38
+ readonly "paymentEscrow": Address;
39
39
  readonly "chainId": number;
40
40
  }
41
41
  export declare const CONTRACTS: Deployments;
@@ -1,40 +1,40 @@
1
1
  // Generated by scripts/build-deployments.mjs — do not edit by hand.
2
2
  // Source: deployments-base-sepolia.json
3
3
  export const CONTRACTS = {
4
- "agentAccountFactory": "0xfCFafCbA6D88351aAAA190a5E971F06CAb40437d",
5
- "agentAccountImplementation": "0x28b7d8e4682EF36bE479330Ba6CFE78E9E724Bc4",
6
- "agentNameRegistry": "0x419928546Be67ba83C2aEaFCb6AD3364D5659C8D",
7
- "agentNameResolver": "0xf96A25DeBF92C514459DC904ec8b79CE52D265A5",
8
- "agentNameUniversalResolver": "0x3aC8Cb8Df1A8F9fcA05a37cEC82426b4432A53D3",
9
- "agentProfileResolver": "0xb988e75179e11E4d17665d61c6a9Ce0e737A808F",
10
- "agentRegistryBase": "0x7fab087fc3d0e1793bdb1d4c5d253635131ee1d1",
11
- "agentRelationship": "0xDc409D69aC002B4FC90D4B453cc6B6277B98CB46",
12
- "agreementRegistry": "0x882371Dc898f4835335a01558aE5C2B306069FcF",
13
- "allowedMethodsEnforcer": "0x1Df87222f53ad26F599C60364F7232a3c0393ad9",
14
- "allowedTargetsEnforcer": "0x55A9fD6aF8688EE9c68D15CDAa84d9DBD027B80F",
15
- "approvedHashRegistry": "0x878272cc4E0c5bA026CEc29C9722F41fC0515fE5",
16
- "attestationRegistry": "0xC695939EA5fB1d7a88BCF536EAC3C13bfcc9E50d",
17
- "callDataHashEnforcer": "0x4ab106b80488FE6A920D973cDb96052a933c7450",
4
+ "agentAccountFactory": "0xE5d2d68b510cC2325fAB5e278855F10DcF02B80A",
5
+ "agentAccountImplementation": "0x3C547Ad14CcB3af20b86D671B0B04E61294ad05F",
6
+ "agentNameRegistry": "0xD9a1eCe15F4265c2f367ccD7Df740baeDc3d0523",
7
+ "agentNameResolver": "0x6E749688a49C3e32B59A54d374D2C116c4Bcc2ea",
8
+ "agentNameUniversalResolver": "0xCbb8C3E3a0C87962dC4831152c936Ca2A8B7826b",
9
+ "agentProfileResolver": "0x821de228dF591089575D9186BF9b5E305f12A221",
10
+ "agentRelationship": "0x1c6D59DD6fd9C7AF39B5afD06FFE7FC032E3608e",
11
+ "agreementRegistry": "0x92c4C46619842A8DDC5F59191d5FDb2c0aFc6874",
12
+ "allowedMethodsEnforcer": "0xb6fa9a218F4d6d3Af3dDaE26E3639a98632d4C09",
13
+ "allowedTargetsEnforcer": "0xca53c91257f6Fa2d1Bc51411B8da40056c861965",
14
+ "approvedHashRegistry": "0x97e322FCDc233Ef4f8f81979D6e12bCdEF226eF0",
15
+ "attestationRegistry": "0x34eF353F7DCdad386b7DD8643248A00bFcE863b8",
16
+ "callDataHashEnforcer": "0x5d8e57091d716A3623c29Bd6CD07A502484dA845",
18
17
  "chainId": 84532,
19
- "custodyPolicy": "0xfAFDc79bc03CC7cE27f7AA12A3180E4E4C0BC3F6",
20
- "delegationManager": "0x10DB7c0C5887910722354ef37eF874fBD124026C",
18
+ "custodyPolicy": "0x7d94B5DAc5d66d30e9D2Ed401BbAB237e86804E6",
19
+ "delegationManager": "0xd494a868B3216A307e9eCAe542D3903e3e364377",
21
20
  "deployer": "0x31ed17fb99e82E02085Ab4B3cbdaB05489098b44",
22
- "entryPoint": "0x86Dc419feDD30F58250B314F5947aDd8cf379436",
23
- "geoFeatureRegistry": "0x1C3b16aF3C2649f2b1ac78aFD6AB20dA0c379121",
24
- "mockUsdc": "0x027de10c034f4055551C32491BCe65711BFE5f69",
25
- "ontologyTermRegistry": "0x95C149Be91c925FB348Fa231aD28440FBaD562cF",
26
- "paymentEnforcer": "0xB09b95E7BcB5d1b3C934facFBEd5bc474b15897E",
27
- "paymentEscrow": "0x053dA1bdD4f0A75b82A75A46F189f137a5267928",
28
- "paymentReceiptRegistry": "0x29645211A44714b06B8c4C55c109Ef285699f56c",
29
- "permissionlessSubregistry": "0x542915A4118A28f49D40DAE53Ee0dc5721Aa4868",
30
- "permissionlessSubregistryDemoAgent": "0x00EB165eA2fd33448bbBd5634a8ba92D616AEc1D",
31
- "quorumEnforcer": "0x093ff9790601F712B3730C3cAa34f9353dC93739",
32
- "relationshipTypeRegistry": "0x0250274222baBB42d44e72e9992DFEB701399a56",
33
- "shapeRegistry": "0x7d0a4e9A981775a3807282bbf3499e77f5E2bA53",
34
- "skillDefinitionRegistry": "0x7be2c5615d83Ca95e02b9BDe24879eb220cb1780",
35
- "smartAgentPaymaster": "0xe93f4ea2cEA8CFF898948c54D6d6144A8F14ca72",
36
- "timestampEnforcer": "0x118A74d6F59ecD9D19BA4b720157a007b6f39894",
37
- "universalSignatureValidator": "0x3Bc1Debf2ac2a59548aC5Ad09404A56bA21b5803",
38
- "valueEnforcer": "0xB7952Ca4a5E10efD24C03c069a0Ea7e262ebE959"
21
+ "entryPoint": "0x9B766cc05BD2193B5bE5dE50DB23DdB5F0B79c00",
22
+ "geoFeatureRegistry": "0x903c289a091CC9200811202De7dF3C22B18325d6",
23
+ "mockUsdc": "0xac4bf60DC027F71e70e7A821df64549e46d72Ed8",
24
+ "ontologyTermRegistry": "0x701F8a8521Dfa814d042781C42fA5e88fbf88F86",
25
+ "paymentEnforcer": "0x4680bcFBB2c2DC40254365300b922659919e3508",
26
+ "paymentReceiptRegistry": "0x496f9B025BB3FBcE8CDB6375176dd30B2eCE5D73",
27
+ "permissionlessSubregistry": "0xB4eF2166CEAccF8b2bbeAC9d0e28f991eb012385",
28
+ "permissionlessSubregistryDemoAgent": "0x857Bec62Dc039A9D06602335d060886206fC0972",
29
+ "quorumEnforcer": "0x38ffeA6A7c332a841E5fb33ED71B0BBF3387aFE1",
30
+ "relationshipTypeRegistry": "0x6E6ec3d0fDA798c86D324F2681ad6E16a7435658",
31
+ "shapeRegistry": "0xe105C57D384a62a3D465Ad6Ca2401B7117bC3415",
32
+ "skillDefinitionRegistry": "0xA00622b30a1cCe201C27A7a9c9dAAe1349492457",
33
+ "smartAgentPaymaster": "0xf40792F5b4d56931B9f77b4ae356fA89eA526224",
34
+ "timestampEnforcer": "0x579e5cDDd94C27688CA5A0DDd2256995e7759280",
35
+ "universalSignatureValidator": "0x01EC03058e98D0E56509E63b20F8454Fc31681F2",
36
+ "valueEnforcer": "0x8cC88ac64F3A4C5784E7F8B41cAd8E17Ea05c34B",
37
+ "agentRegistryBase": "0x1f8609F11fcaf0666534579EABe8B39778DFEdaA",
38
+ "paymentEscrow": "0xF523F7a30b22F35B2Ee506392cfA16BD8FEEe411"
39
39
  };
40
40
  export default CONTRACTS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticprimitives/contracts",
3
- "version": "1.0.0-alpha.14",
3
+ "version": "1.0.0-alpha.16",
4
4
  "description": "Solidity contracts + ABIs + flattened sources + per-network deployment addresses for the agenticprimitives stack.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -547,25 +547,39 @@ contract AgentAccount is
547
547
  _upgradeAuthorizedCtx = false;
548
548
  }
549
549
 
550
- /// @notice Cancel a queued upgrade during the timelock window. Owner
551
- /// signs a digest that binds to the pending implementation
552
- /// AND `address(this)` + chain id, preventing cross-account
553
- /// replay of a cancel-grant.
550
+ /// @notice Cancel a queued upgrade during the timelock window.
551
+ /// @dev NEW-CANCEL-UPGRADE-1: cancel is a governance op. On a CUSTODY-GOVERNED
552
+ /// account (mode>0 / org) it MUST go through the custody quorum — routed via
553
+ /// the CustodyPolicy executor (`_custodyGovCtx`), exactly like `scheduleUpgrade`
554
+ /// and the other governance mutators — so a single compromised custodian's
555
+ /// ECDSA signature cannot perpetually cancel quorum-scheduled upgrades
556
+ /// (upgrade-liveness DoS). The lightweight ECDSA cancel-grant fast-path remains
557
+ /// ONLY for non-governed (mode-0) accounts, where the sole custodian IS the owner.
554
558
  /// @param ownerSig Owner ECDSA signature over `keccak256(abi.encode(
555
559
  /// "UPGRADE_CANCEL", _pendingUpgrade.newImplementation,
556
- /// address(this), block.chainid))`.
560
+ /// address(this), block.chainid))`. Ignored on governed accounts
561
+ /// (the quorum context authorizes; no signature is consulted).
557
562
  function cancelPendingUpgrade(bytes calldata ownerSig) external {
563
+ bool governed = _hasCustodyGovernance();
564
+ // Governed: authorize FIRST (fail-closed before revealing pending state). Only the custody quorum
565
+ // (executor self-call with the gov context set) may cancel — `_custodyGovCtx` is set solely by
566
+ // `executeFromModule` when the caller is the CustodyPolicy module, so this reverts
567
+ // AdminRequiresCustodyQuorum for any single-custodian / external attempt.
568
+ if (governed) _requireCustodyGoverned();
558
569
  PendingUpgrade memory p = _pendingUpgrade;
559
570
  if (p.readyAt == 0) revert NoPendingUpgrade();
560
- bytes32 digest = keccak256(
561
- abi.encode(
562
- bytes32("UPGRADE_CANCEL"),
563
- p.newImplementation,
564
- address(this),
565
- block.chainid
566
- )
567
- );
568
- if (!_verifyEcdsa(digest, ownerSig)) revert NotOwnerSig();
571
+ if (!governed) {
572
+ // Non-governed (mode-0): the sole custodian's ECDSA cancel-grant authorizes.
573
+ bytes32 digest = keccak256(
574
+ abi.encode(
575
+ bytes32("UPGRADE_CANCEL"),
576
+ p.newImplementation,
577
+ address(this),
578
+ block.chainid
579
+ )
580
+ );
581
+ if (!_verifyEcdsa(digest, ownerSig)) revert NotOwnerSig();
582
+ }
569
583
  delete _pendingUpgrade;
570
584
  emit UpgradeCancelled(p.newImplementation);
571
585
  }
@@ -1028,18 +1042,30 @@ contract AgentAccount is
1028
1042
  revert NotInstalledExecutor(msg.sender);
1029
1043
  }
1030
1044
  emit ModuleExecuted(msg.sender, target, value);
1031
- // CA-1 + Wave-1/ACCT-C1 (spec 300): a module action is a custody-governed
1032
- // context (only an installed EXECUTOR — the CustodyPolicy — reaches here,
1033
- // gated upstream by its own T5 quorum + timelock). `_custodyGovCtx` authorizes
1034
- // every governance mutator this call dispatches (the ONLY way past
1035
- // `_requireCustodyGoverned` on a `mode>0` account); `_upgradeAuthorizedCtx`
1036
- // additionally exempts a dispatched upgrade from the per-account timelock (no
1037
- // double delay). Both transient, cleared after the call.
1038
- _custodyGovCtx = true;
1039
- _upgradeAuthorizedCtx = true;
1045
+ // CA-1 + Wave-1/ACCT-C1 (spec 300): a module action from the CANONICAL CustodyPolicy is a
1046
+ // custody-governed context (gated upstream by its own T5 quorum + timelock). `_custodyGovCtx`
1047
+ // authorizes every governance mutator this call dispatches (the ONLY way past
1048
+ // `_requireCustodyGoverned` on a `mode>0` account); `_upgradeAuthorizedCtx` additionally exempts a
1049
+ // dispatched upgrade from the per-account timelock (no double delay). Both transient.
1050
+ //
1051
+ // NEW-EXEC-MODULE-1 (2026-07-05 audit, internal + external): grant that context ONLY to the
1052
+ // canonical CustodyPolicy the factory installs — NOT to any installed executor. Otherwise a second
1053
+ // (utility / automation) executor a quorum installs believing it unprivileged would silently inherit
1054
+ // full authority to mutate custody / DM / upgrade / modules, bypassing quorum+timelock. Matching the
1055
+ // specific policy address is the same check `_hasCustodyGovernance` uses. A non-canonical executor
1056
+ // runs WITHOUT the context, so `_requireCustodyGoverned` rejects any governance mutator it dispatches
1057
+ // (defense-in-depth: not reachable at v1 since the factory installs only CustodyPolicy).
1058
+ address f = _factory;
1059
+ bool custodyGoverned = f != address(0) && msg.sender == IAgentAccountFactoryView(f).custodyPolicy();
1060
+ if (custodyGoverned) {
1061
+ _custodyGovCtx = true;
1062
+ _upgradeAuthorizedCtx = true;
1063
+ }
1040
1064
  (bool ok, bytes memory ret) = target.call{value: value}(data);
1041
- _upgradeAuthorizedCtx = false;
1042
- _custodyGovCtx = false;
1065
+ if (custodyGoverned) {
1066
+ _upgradeAuthorizedCtx = false;
1067
+ _custodyGovCtx = false;
1068
+ }
1043
1069
  if (!ok) {
1044
1070
  assembly { revert(add(ret, 32), mload(ret)) }
1045
1071
  }
@@ -1465,6 +1491,19 @@ contract AgentAccount is
1465
1491
  return _passkeyStorage().piaToCredentialId[account] != bytes32(0);
1466
1492
  }
1467
1493
 
1494
+ /// @notice EXT-ISCUSTODIAN-1 (2026-07-05 audit): like `isCustodian` but EXCLUDES the account itself.
1495
+ /// @dev `isCustodian(address(this)) == true` is deliberate (account-actor compatibility), but that
1496
+ /// footgun can recreate the ACCT-C2 quorum-collapse class in a FUTURE consumer that reuses
1497
+ /// `isCustodian` as if it meant "external signer." Any quorum / authority consumer that must count
1498
+ /// only real external custodians / passkeys (never the account) should use THIS view. ACCT-C2 fixed
1499
+ /// the current CustodyPolicy quorum at the counting site (reject signer==account); this gives new
1500
+ /// consumers the safe primitive so the class can't reappear off-path.
1501
+ function isExternalCustodianOrPasskey(address account) external view returns (bool) {
1502
+ if (account == address(this)) return false;
1503
+ if (_externalCustodians[account]) return true;
1504
+ return _passkeyStorage().piaToCredentialId[account] != bytes32(0);
1505
+ }
1506
+
1468
1507
  /// @inheritdoc IAgentAccount
1469
1508
  /// @dev Counts EOA/contract custodians PLUS registered passkeys —
1470
1509
  /// each passkey contributes one PIA-custodian. Matches the
@@ -80,6 +80,10 @@ contract AgentAccountFactory is GovernanceManaged {
80
80
  /// @notice ACCT-M1 (spec 300): a mode-3 (org) account needs ≥2 signers so its separation-of-duties
81
81
  /// (disjoint propose + apply quorums) is satisfiable — a 1-signer org can never split duties.
82
82
  error InfeasibleOrgConfig(uint8 nSigners);
83
+ /// @notice EXT-TL-FLOOR-1 (external audit P1-2): a mode>0 timelock override is below the on-chain product
84
+ /// floor. Overrides may RAISE a safety delay but never LOWER it below the marketed minimum
85
+ /// (spec 300 — the T5/recovery guarantee is a protocol invariant, not a UI convention).
86
+ error TimelockBelowFloor(uint8 tier, uint32 provided, uint32 floorSecs);
83
87
  error ZeroAddress();
84
88
  /// @notice R9.6 / ATL-SEC-05 -- thrown when `params.custodians.length`
85
89
  /// exceeds `MAX_INITIAL_CUSTODIANS`. Without this cap,
@@ -329,6 +333,15 @@ contract AgentAccountFactory is GovernanceManaged {
329
333
  timelocks[5] = timelockOverrides[5] == 0 ? uint32(24 hours) : timelockOverrides[5];
330
334
  timelocks[6] = timelockOverrides[6] == 0 ? uint32(48 hours) : timelockOverrides[6];
331
335
 
336
+ // EXT-TL-FLOOR-1 (external audit P1-2): a timelock override may RAISE a safety delay but NEVER lower it
337
+ // below the product floor. A zero override inherits the floor default above; a non-zero override below
338
+ // the floor reverts, so a mode>0 (custody-governed) account can never deploy with sub-floor T4/T5/T6
339
+ // (recovery = T6) delays. The "T5=24h / recovery=48h" guarantee becomes a protocol invariant, not a UI
340
+ // convention (spec 300 — raise-only). Demo/testnet fast-timelock deploys use zero overrides (defaults).
341
+ if (timelocks[4] < 1 hours) revert TimelockBelowFloor(4, timelocks[4], uint32(1 hours));
342
+ if (timelocks[5] < 24 hours) revert TimelockBelowFloor(5, timelocks[5], uint32(24 hours));
343
+ if (timelocks[6] < 48 hours) revert TimelockBelowFloor(6, timelocks[6], uint32(48 hours));
344
+
332
345
  uint8 recThr = uint8(params.trustees.length / 2 + 1);
333
346
 
334
347
  return abi.encode(
@@ -109,6 +109,10 @@ contract DelegationManager is IDelegationManager, ReentrancyGuard {
109
109
  // Phase A.5 errors.
110
110
  error NotDelegatorOrDelegate();
111
111
  error HashMismatch();
112
+ /// @dev EXT-NOCODE-HOOK-1: a caveat enforcer with no code — a call to it would
113
+ /// succeed vacuously (fail-OPEN), so the live before/after hook loops reject it,
114
+ /// matching the view path (`verifyAuthorizationForCall` → "no-code-enforcer").
115
+ error NoCodeEnforcer(address enforcer);
112
116
  /// @dev Thrown by the deprecated permissionless `revokeDelegation(bytes32)`
113
117
  /// path. Migrate callers to `revokeDelegationByOwner(Delegation)`.
114
118
  error LegacyRevocationDisabled();
@@ -454,6 +458,9 @@ contract DelegationManager is IDelegationManager, ReentrancyGuard {
454
458
  ) internal {
455
459
  bytes32 dHash = hashDelegation(d);
456
460
  for (uint256 j = 0; j < d.caveats.length; j++) {
461
+ // EXT-NOCODE-HOOK-1: fail closed on a no-code enforcer (a call would succeed vacuously),
462
+ // symmetric with the view path's "no-code-enforcer" reject — never rely on call semantics.
463
+ if (d.caveats[j].enforcer.code.length == 0) revert NoCodeEnforcer(d.caveats[j].enforcer);
457
464
  ICaveatEnforcer(d.caveats[j].enforcer).beforeHook(
458
465
  d.caveats[j].terms,
459
466
  d.caveats[j].args,
@@ -475,6 +482,8 @@ contract DelegationManager is IDelegationManager, ReentrancyGuard {
475
482
  ) internal {
476
483
  bytes32 dHash = hashDelegation(d);
477
484
  for (uint256 j = 0; j < d.caveats.length; j++) {
485
+ // EXT-NOCODE-HOOK-1: fail closed on a no-code enforcer, symmetric with the before-hook + view path.
486
+ if (d.caveats[j].enforcer.code.length == 0) revert NoCodeEnforcer(d.caveats[j].enforcer);
478
487
  ICaveatEnforcer(d.caveats[j].enforcer).afterHook(
479
488
  d.caveats[j].terms,
480
489
  d.caveats[j].args,
@@ -318,6 +318,10 @@ contract CustodyPolicy {
318
318
  error TrusteeDoesNotExist(address guardian);
319
319
  error InvalidMode(uint8 mode_);
320
320
  error InvalidThresholdValue(uint8 thr);
321
+ /// @dev EXT-CHANGEMODE-1: a runtime ChangeMode into org mode (3) with an infeasible signer
322
+ /// count (< 2, so separation-of-duties can't split proposers from appliers). Mirrors the
323
+ /// factory's install-time InfeasibleOrgConfig.
324
+ error InfeasibleOrgConfig(uint8 nSigners);
321
325
  /// @dev CP-1: a tier that must be configured (T4/T5 at install, T6 at read)
322
326
  /// was left unset — refuse rather than fall to the 1-of-n read fallback.
323
327
  error UnconfiguredTier(uint8 tier);
@@ -884,6 +888,23 @@ contract CustodyPolicy {
884
888
  if (newMode > 3) revert InvalidMode(newMode);
885
889
  uint8 oldMode = c.mode;
886
890
  if (newMode == 0 && c.trusteeCount > 0) revert CannotDowngradeWithTrustees();
891
+ // EXT-CHANGEMODE-1 (2026-07-05 audit): a runtime ChangeMode must re-run the factory's install-time
892
+ // mode-3 feasibility so the account can't enter an un-executable / liveness-hostile org mode.
893
+ // Org SoD needs ≥2 signers (distinct proposers + appliers) and T4/T5 thresholds within the
894
+ // floor(N/2) cap (else 2×threshold > N → no disjoint quorum → the tier silently locks). Require the
895
+ // signer floor; cap the SoD-constrained thresholds (mirrors the factory, avoids a locked mode).
896
+ if (newMode == 3) {
897
+ uint256 n = IAgentAccount(account).custodianCount();
898
+ if (n < 2) revert InfeasibleOrgConfig(uint8(n));
899
+ uint8 sodCap = uint8(n / 2); // ≥ 1 since n ≥ 2
900
+ for (uint8 tier = 4; tier <= 5; tier++) {
901
+ uint8 v = c.approvalsRequiredByTier[tier];
902
+ if (v > sodCap) {
903
+ c.approvalsRequiredByTier[tier] = sodCap;
904
+ emit ThresholdChanged(account, tier, v, sodCap);
905
+ }
906
+ }
907
+ }
887
908
  c.mode = newMode;
888
909
  emit ModeChanged(account, oldMode, newMode);
889
910
  }
@@ -968,6 +989,13 @@ contract CustodyPolicy {
968
989
  if (newCount == 0) revert InvalidThresholdValue(newCount);
969
990
  uint256 n = IAgentAccount(account).custodianCount();
970
991
  if (uint256(newCount) > n) revert InvalidThresholdValue(newCount);
992
+ // acct-mode3-sod-cap-runtime (2026-07-05 audit): the factory caps mode-3 (org) T4/T5 thresholds at
993
+ // floor(N/2) at install so a DISJOINT propose+apply quorum always exists (2×threshold ≤ N). A later
994
+ // ChangeApprovalsRequired must not raise them past that cap and silently lock the tier — reject an
995
+ // explicit over-cap request loud (the org can lower N-constraints or use a lower threshold).
996
+ if (c.mode == 3 && (tier == 4 || tier == 5) && uint256(newCount) > n / 2) {
997
+ revert InvalidThresholdValue(newCount);
998
+ }
971
999
  uint8 oldValue = c.approvalsRequiredByTier[tier];
972
1000
  if (oldValue == 0) oldValue = 1; // mirror _approvalsValue fallback
973
1001
  c.approvalsRequiredByTier[tier] = newCount;