feat(contracts): TEE-attested service approval commitments#117
Conversation
✅ No Blockers —
|
| kimi | glm | aggregate | |
|---|---|---|---|
| Readiness | 87 | 92 | 87 |
| Confidence | 95 | 95 | 95 |
| Correctness | 92 | 95 | 92 |
| Security | 91 | 94 | 91 |
| Testing | 93 | 95 | 93 |
| Architecture | 88 | 93 | 88 |
Read every changed file. The TEE commitment feature is well-structured: auth-before-writes ordering is correct,
nonReentrantguard is in place, storage gap is correctly reduced by 2 for 2 new mappings, nonce replay prevention is sound, gas-griefing cap is justified and documented, and the request→service migration ordering comment is accurate. One documentation bug in Errors.sol will cause off-chain integrators to derive the wrong nonce and hit silent reverts; everything else is nits or consistent with existing codebase patterns. | Read all 8 changed files in full, verified all 23 tests pass
🟠 MEDIUM Errors.InvalidNonceBinding comment has wrong nonce formula — integrators will always compute the wrong value — src/libraries/Errors.sol
Line 455: the NatSpec states
nonceBinding MUST equal keccak256(abi.encode(requestId, address(this), block.chainid)). The actual implementation inServicesApprovals.teeNonceFor(line 315) iskeccak256(abi.encode("tangle.tee.nonce", requestId, address(this), block.chainid)). The domain separator"tangle.tee.nonce"is omitted from the comment. Any operator SDK or off-chain tool that follows this comment verbatim will produce a different 32-byte value and receiveInvalidNonceBindingon every submission — silently, with no indication that the fo
🟡 LOW Double O(n) operator-list scan per approveServiceWithTeeCommitments call — src/core/ServicesApprovals.sol
Lines 208 and 349:
_requireApprovingOperatoriterates_requestOperators[requestId]to confirmmsg.senderis in the list, then_approveServiceWithCommitmentsInternal(called at line 218) repeats the identical loop at line 349. Under thenonReentrantguard no state can change between the two calls, so the second scan is pure waste — linear in operator count per approval. Existing entrypoints (`approveServ
🟡 LOW _requestTeeCommitments never deleted after activation — orphaned storage slots — src/facets/tangle/TangleServicesFacet.sol
Lines 337-348:
_persistTeeCommitmentscopies commitment arrays from_requestTeeCommitments[requestId][op]to_serviceTeeCommitments[serviceId][op]but never deletes the source. At the documented ~60K gas per entry and up to 8 entries × N operators, the orphaned slots are non-trivial. This is consistent with_persistResourceCommitments(line 294-311) which also leaves_requestResourceRequirementsintact, so it matches the existing design choice — but both forfeit SSTORE refunds. Worth a one-line `d
🟡 LOW Errors.InvalidNonceBinding @dev comment omits domain prefix — src/libraries/Errors.sol
Line 454-456: The @dev comment says
nonceBinding MUST equal keccak256(abi.encode(requestId, address(this), block.chainid))but the actual implementation in ServicesApprovals.sol:314-316 useskeccak256(abi.encode("tangle.tee.nonce", requestId, address(this), block.chainid)). An integrator reading the error doc to compute the canonical nonce would derive the wrong value and get rejected. Fix: add the 'tangle.tee.nonce' prefix to the @dev comment.
tangletools · 2026-05-04T20:12:34Z · trace
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 5 non-blocking findings
The AppRegistry and TEE attestation commitment features (6 of 14 changed files) are clean, well-tested, and architecturally sound. However this PR also bundles four serious regressions in the governance, rewards, and streaming-payment layers: the TangleTimelock._setMinDelay function now writes to hardcoded slot 0x33 instead of the correct ERC-7201 namespaced slot, meaning updateDelay silently fails to persist; the updateDelay auth model was downgraded from self-call to DEFAULT_ADMIN_ROLE (governance bypass); nonReentrant was removed from two load-bearing streaming-payment drip functions; and I
🟠 MEDIUM TeeAttestationCommitment.nonceBinding not validated — anti-replay protection is opt-in — src/core/ServicesApprovals.sol
Lines 231-248 (
_validateTeeCommitments): validatesbackend != DirectTdxandexpiresAt > now, but does not checknonceBinding != bytes32(0). TheTypes.TeeAttestationCommitmentNatSpec (Types.sol) documents: 'Caller-supplied nonce that binds this commitment to a specific challenge ... so a stale attestation cannot be replayed against a fresh request.' If an operator suppliesnonceBinding = 0, the commitment has no challenge binding and a recycled attestation from an old service can satisfy it. Add:if (teeCommitments[i].nonceBinding == bytes32(0)) revert Errors.InvalidNonceBinding().
🟠 MEDIUM AppRegistry.listApps returns revoked apps — callers must do a second lookup to filter — src/registry/AppRegistry.sol
Line 357 (
revokeApp): setsrecord.revoked = truebut does not removeappIdfrom_namespaceApps[namespace].listApps(line 368) returns_namespaceApps[namespace].values()which includes revoked apps. Any consumer iteratinglistAppsresults and treating them as active apps will silently include revoked entries. Fix: either remove the appId from_namespaceAppsinrevokeApp, or add alistActiveAppsview that filters onrevoked.
🟡 LOW Two error types defined but never used in changed code — src/libraries/Errors.sol
Lines 455 and 459: MeasurementMismatch and TeeCommitmentLengthMismatch are declared in the new TEE section but are never referenced by any contract in this PR. The MeasurementMismatch comment says 'Reserved for off-chain / blueprint use' which is fine for forward-compatibility, but TeeCommitmentLengthMismatch has no apparent caller at all. If these are genuinely needed by off-chain code (e.g. replica verification in a blueprint), that should be documented — otherwise they're dead code adding to the ABI/bytecode.
🟡 LOW AppRecord.verified is stale cached storage — getApp bypasses it anyway — src/registry/AppRegistry.sol
Lines 207-218 (
publishApp) and 237 (pinAppVersion):record.verified = _namespaceVerified[namespace]writes the verified flag intoAppRecordstorage on every publish and pin. ButgetApp(line 302) returnsverified = _namespaceVerified[namespace], reading directly from the source mapping and ignoringrecord.verified. The cached field is written on every publish/pin (~5k gas SSTORE), never read, and is stale between pins. Remove therecord.verifiedfield and its writes to save gas and eliminate the misleading cached state.
🟡 LOW appCount uses EnumerableSet.length() which iterates O(n) — src/registry/AppRegistry.sol
Line 375-376: appCount returns _namespaceApps[namespace].length() which iterates the entire EnumerableSet to count elements. If a namespace accumulates many apps, this becomes expensive. A separate counter mapping (incremented on publish, decremented on any future removal) would provide O(1). Not a blocker since there's no app removal path yet, but worth noting for future scale.
tangletools · 2026-05-04T18:20:59Z · trace
cbdf527 to
11e89d4
Compare
11e89d4 to
028db05
Compare
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 4 non-blocking findings
Read all 7 changed files plus the full ServicesApprovals.sol and TangleServicesFacet._activateService flow. The core logic is correct: TEE commitments are stored to _requestTeeCommitments before _approveServiceWithCommitmentsInternal triggers activation, which copies them into _serviceTeeCommitments via _persistTeeCommitments — the ordering is intentional and correct. Storage gap math (44→42 for 2 new mapping slots) is right. ABI selector keccak string matches the canonical tuple encoding. The one structural concern is that _storeRequestTeeCommitments performs SSTOREs before the operator-autho
🟠 MEDIUM Authorization check follows storage write — breaks validate-before-write — src/core/ServicesApprovals.sol
In approveServiceWithTeeCommitments (lines 208–214), _storeRequestTeeCommitments (which does SSTORE pushes) is called before _approveServiceWithCommitmentsInternal, which is where the operator-in-request-list check and AlreadyApproved guard live. An unauthorized or already-approved caller executes the storage loop before the transaction reverts; the writes are rolled back, so there is no persistent corruption, but the caller burns gas on the loop and the pattern is wrong. Fix: move _validateTeeCommitments + BLS check before the storage write, and restructure so authorization is confirmed before any state mutation.
🟡 LOW _requestTeeCommitments not reclaimed after activation or rejection — src/facets/tangle/TangleServicesFacet.sol
_persistTeeCommitments (line ~333–347) copies from _requestTeeCommitments to _serviceTeeCommitments but never deletes the source. After activation the request-scoped storage is never accessed again (comment in TangleStorage.sol confirms this) but the slots remain warm, costing refund gas that is never collected. The same pattern exists for other request-scoped maps (consistent), but note that deleting inside _persistTeeCommitments after the copy would reclaim ~4700 gas per non-zero slot. Low priority given consistency with the rest of the codebase.
🟡 LOW TeeCommitmentLengthMismatch error declared but never used — src/libraries/Errors.sol
TeeCommitmentLengthMismatch(uint256 commitmentCount, uint256 teeCommitmentCount) is declared at the bottom of the TEE section but is referenced nowhere in this PR (no call site in ServicesApprovals.sol or TangleServicesFacet.sol). If the per-operator commitment-count enforcement it was intended to guard was dropped, delete the error. If the enforcement is still planned, add the check.
🟡 LOW No test for non-TEE approval path producing empty getTeeCommitment result — test/tangle/TeeCommitmentApprovalTest.t.sol
All tests use approveServiceWithTeeCommitments. There is no test that verifies an operator who approved via approveService, approveServiceWithBls, or approveServiceWithCommitmentsAndBls results in getTeeCommitment returning an empty array after activation. This exercises the 'if (len == 0) continue' branch in _persistTeeCommitments for the non-TEE path and confirms the storage separation is correct.
tangletools · 2026-05-04T18:45:32Z · trace
tangletools
left a comment
There was a problem hiding this comment.
❌ 1 Blocking Finding
The TEE commitment feature is architecturally clean — correct storage layout, proper ordering of TEE writes before the activation-triggering approval call, correct storage gap reduction (44→42 for two new mappings), and a well-structured test suite that covers the primary error paths. One high-severity issue undermines the rating: there is no upper bound on the
teeCommitmentscalldata array, which lets a malicious operator in a multi-operator service submit a gas-exhausting array that permanently bricks service activation when the last legitimate operator triggers it. Two medium gaps round o
🔴 HIGH Unbounded teeCommitments array enables permanent activation DoS in multi-operator services — src/core/ServicesApprovals.sol
Lines 208–214 and 239–252:
_validateTeeCommitmentsand_storeRequestTeeCommitmentsiterate overteeCommitmentswith no length cap. Each entry pushes to_requestTeeCommitments[requestId][operator](cold SSTORE ~20K gas per slot; struct is 3 slots → ~60K gas/entry). These stored entries are later bulk-copied in_persistTeeCommitments(TangleServicesFacet.sol:336–347), which runs inside_activateServicetriggered by whoever submits the final approval. A malicious operator in a 2+ operator service can submit ~400 entries in their own approval tx (staying under block gas limit), then the final legitimate operator's approval will hit the gas limit dur
4 Non-Blocking
🟠 MEDIUM Zero expectedMeasurement silently accepted — src/core/ServicesApprovals.sol
Lines 220–236:
_validateTeeCommitmentsrejectsnonceBinding == bytes32(0)(line 228) withInvalidNonceBinding, but does not rejectexpectedMeasurement == bytes32(0). An operator submitting a zero measurement commits to a workload whose measurement is zero — practically impossible for any real hash function output, but semantically a commitment that can never be satisfied. An off-chain provisioning oracle comparingattestedMeasurement == expectedMeasurementagainst a zero value would either always fail (stalling the service)
🟠 MEDIUM No test for mixed TEE / non-TEE approvals in a multi-operator service — test/tangle/TeeCommitmentApprovalTest.t.sol
All multi-operator test cases (e.g.
test_multipleOperators_distinctBackends_persistedPerOperator, line 185) send every operator throughapproveServiceWithTeeCommitments. The_persistTeeCommitmentsfunction in TangleServicesFacet.sol:336–347 has explicit logic to skip operators with no stored commitments (if (len == 0) continue;), but this branch is never exercised. The production scenario where operator 1 usesapproveServiceWithTeeCommitmentsand operator 2 uses plainapproveServiceWithCommitmentsis unverified; a bug in the skip logic or in how activation counts operators would not be caught.
🟡 LOW Duplicate TeeBackend entries silently accepted — src/core/ServicesApprovals.sol
Lines 220–236:
_validateTeeCommitmentsdoes not check for duplicatebackendvalues across entries. An operator can submit two entries withbackend == TeeBackend.AwsNitroand different (or identical) measurements; both are stored and twoTeeCommitmentRecordedevents are emitted. The NatSpec says 'any one matching satisfies the policy,' which is consistent with duplicates being harmless at the protocol level, but they waste storage and inflate event logs. If the intent is per-backend commitment, add a seen-bitmap check during validation.
🟡 LOW TeeBackend enum default is Phala (0), not caught by validation — src/libraries/Types.sol
The TeeBackend enum starts at Phala=0, so a struct where
backendis accidentally left unset defaults to Phala. ThenonceBinding==0check at src/core/ServicesApprovals.sol:228 catches the all-zero case, but if an integrator sets nonceBinding and expectedMeasurement while forgetting backend, it silently accepts Phala. Not exploitable in practice but a footgun. Consider starting the enum at 1 with a sentinel value likeUnset.
tangletools · 2026-05-04T19:04:47Z · trace
028db05 to
bce6788
Compare
|
Addressed all five findings from the latest review (commit 🔴 HIGH — Unbounded
|
Workstream C from the Tangle Cloud app-store program plan
(docs/blueprint-app-store-program-plan.md in dapp).
Operators may now commit to a specific TEE backend + measurement +
nonce-binding at approval time. Blueprints (or off-chain provisioning
oracles) cross-check the live attestation against the committed value
before accepting the provision result.
Types (`src/libraries/Types.sol`)
- `enum TeeBackend { Phala, AwsNitro, GcpConfidential, AzureSkr, DirectTdx }`
— append-only; `DirectTdx` recognised but rejected at approval (mirror
of ai-agent-sandbox-blueprint #50 policy).
- `struct TeeAttestationCommitment { backend, expectedMeasurement, nonceBinding, expiresAt }`.
Approval entrypoint (`src/core/ServicesApprovals.sol`)
- `approveServiceWithTeeCommitments(requestId, AssetSecurityCommitment[],
blsPubkey, popSignature, TeeAttestationCommitment[])` — validates each
TEE commitment (rejects DirectTdx, rejects past expiries), stores the
per-operator array in `_requestTeeCommitments`, and emits
`TeeCommitmentRecorded`.
Activation persistence (`src/facets/tangle/TangleServicesFacet.sol`)
- `_activateService` calls `_persistTeeCommitments`, copying every
approving operator's commitment list onto `_serviceTeeCommitments[serviceId]`.
- New view `getTeeCommitment(serviceId, operator)` exposes the array
for blueprint provisioning hooks. Selector list grows 7 → 9.
Storage (`src/TangleStorage.sol`)
- Two new mappings: `_requestTeeCommitments[requestId][operator]` and
`_serviceTeeCommitments[serviceId][operator]`. `__gap` reduced 44 → 42.
Errors (`src/libraries/Errors.sol`)
- `DirectTdxNotPermitted`, `TeeCommitmentExpired(uint64,uint64)`,
`MeasurementMismatch(bytes32,bytes32)`, `TeeCommitmentLengthMismatch(uint256,uint256)`.
Interface (`src/interfaces/ITangleServices.sol`)
- New entrypoint declaration.
Tests (`test/tangle/TeeCommitmentApprovalTest.t.sol`, 9 cases)
- Happy path: Nitro commitment recorded, fetched via view.
- DirectTdx rejected at first slot and at later slot.
- Past-expiry and at-current-timestamp expiry rejected.
- Future expiry persists through activation.
- Empty TEE array passes through with no persistence.
- Multiple operators with distinct backends persisted per-operator.
- Unknown-operator view returns empty array.
Why this surface and not the existing `OperatorSecurityCommitment`:
TEE commitments have a different shape (backend enum + 32-byte
measurement + 32-byte nonce + uint64 expiry) and a different lifecycle
(expiry checked at approval; cross-check against live attestation by
blueprint). Folding into the existing AssetSecurityCommitment array
would require a discriminant tag and would muddle the per-asset vs
per-attestation storage layouts. A parallel storage slot keeps both
clean.
bce6788 to
57b333d
Compare
Superseded by re-review — no blocking findings on latest commit.
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 4 non-blocking findings
Read all 7 changed files plus Base.sol/_getServiceRequest, TangleServicesFacet._activateService, and the full ServicesApprovals context. No critical or high bugs found. Storage gap math is correct (2 new mappings → gap reduced 44→42). Auth-first ordering is sound, DoS cap is justified, and all six validation rules in _validateTeeCommitments are semantically correct. Two medium/low findings: a double-auth-check in the new entrypoint wastes ~8 K gas on the happy path, and _persistTeeCommitments never deletes _requestTeeCommitments source slots after copying (forfeits storage refund). One enum ba
🟠 MEDIUM Double auth check in approveServiceWithTeeCommitments wastes gas — src/core/ServicesApprovals.sol
approveServiceWithTeeCommitments (line 208) calls _requireApprovingOperator(requestId) which reads four storage slots (req.rejected, isOperatorActive, _requestOperators[requestId], _requestApprovals[requestId][msg.sender]), then calls _approveServiceWithCommitmentsInternal (line 218) which reads the same four slots again (lines 309-325). The only state change between the two reads is the TEE commitment write
🟡 LOW TEE section in TangleStorage.sol lacks slot-range comment — src/TangleStorage.sol
Every other storage section in TangleStorage.sol carries a slot-range annotation (e.g. '// PRICING STORAGE (Slot 136-140)'). The new TEE ATTESTATION COMMITMENTS section (lines 372-383) has no such annotation, making it harder to track the cumulative slot count when adding future storage. Add the slot range to match the surrounding convention.
🟡 LOW _persistTeeCommitments does not delete source slots — no storage refund — src/facets/tangle/TangleServicesFacet.sol
_persistTeeCommitments (line 334) copies each TeeAttestationCommitment element from _requestTeeCommitments[requestId][op] into _serviceTeeCommitments[serviceId][op] but never calls delete or pop on the source array. Each struct occupies 4 storage slots (backend/uint8, expectedMeasurement/bytes32, nonceBinding/bytes32, expiresAt/uint64). With up to 8 elements per operator and N operators, the request-scoped data (up to 32×N slots) is permanently wasted. This is consistent with the existing _persistServiceSecurity pattern, but with TEE commitments there are potentially more slots per operator. Consider deleting the source array after copy or documenting t
🟡 LOW AzureSkr backend has zero test coverage — test/tangle/TeeCommitmentApprovalTest.t.sol
Types.TeeBackend.AzureSkr (enum value 4) is defined in Types.sol (line ~305) and is accepted by _validateTeeCommitments (not Unset, not DirectTdx), but no test in TeeCommitmentApprovalTest exercises it. Phala, AwsNitro, GcpConfidential, and DirectTdx are all covered. Add a test mirroring test_approve_withNitroCommitment_persistsOnActivation but using TeeBackend.AzureSkr to confirm it round-trips correctly through storage.
tangletools · 2026-05-04T19:53:32Z · trace
|
Adversarial sweep on top of the review-fix commit ( T1 [HIGH] —
|
CI status — pre-existing OOM, not caused by this PRRun 25340146909 failed at the "Run Forge build" step with exit code 143 (SIGTERM, OOM-kill) after 17m17s of compilation. The job was killed mid-compile of 342 Solidity files; no test ever ran. This is the same failure mode as:
Cause: Local reproduction is green: This PR does not change The TEE commitment code itself + the harden fixes are verified locally and via PR review. |
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 4 non-blocking findings
Read every changed file. The TEE commitment feature is well-structured: auth-before-writes ordering is correct,
nonReentrantguard is in place, storage gap is correctly reduced by 2 for 2 new mappings, nonce replay prevention is sound, gas-griefing cap is justified and documented, and the request→service migration ordering comment is accurate. One documentation bug in Errors.sol will cause off-chain integrators to derive the wrong nonce and hit silent reverts; everything else is nits or consistent with existing codebase patterns. | Read all 8 changed files in full, verified all 23 tests pass
🟠 MEDIUM Errors.InvalidNonceBinding comment has wrong nonce formula — integrators will always compute the wrong value — src/libraries/Errors.sol
Line 455: the NatSpec states
nonceBinding MUST equal keccak256(abi.encode(requestId, address(this), block.chainid)). The actual implementation inServicesApprovals.teeNonceFor(line 315) iskeccak256(abi.encode("tangle.tee.nonce", requestId, address(this), block.chainid)). The domain separator"tangle.tee.nonce"is omitted from the comment. Any operator SDK or off-chain tool that follows this comment verbatim will produce a different 32-byte value and receiveInvalidNonceBindingon every submission — silently, with no indication that the fo
🟡 LOW Double O(n) operator-list scan per approveServiceWithTeeCommitments call — src/core/ServicesApprovals.sol
Lines 208 and 349:
_requireApprovingOperatoriterates_requestOperators[requestId]to confirmmsg.senderis in the list, then_approveServiceWithCommitmentsInternal(called at line 218) repeats the identical loop at line 349. Under thenonReentrantguard no state can change between the two calls, so the second scan is pure waste — linear in operator count per approval. Existing entrypoints (`approveServ
🟡 LOW _requestTeeCommitments never deleted after activation — orphaned storage slots — src/facets/tangle/TangleServicesFacet.sol
Lines 337-348:
_persistTeeCommitmentscopies commitment arrays from_requestTeeCommitments[requestId][op]to_serviceTeeCommitments[serviceId][op]but never deletes the source. At the documented ~60K gas per entry and up to 8 entries × N operators, the orphaned slots are non-trivial. This is consistent with_persistResourceCommitments(line 294-311) which also leaves_requestResourceRequirementsintact, so it matches the existing design choice — but both forfeit SSTORE refunds. Worth a one-line `d
🟡 LOW Errors.InvalidNonceBinding @dev comment omits domain prefix — src/libraries/Errors.sol
Line 454-456: The @dev comment says
nonceBinding MUST equal keccak256(abi.encode(requestId, address(this), block.chainid))but the actual implementation in ServicesApprovals.sol:314-316 useskeccak256(abi.encode("tangle.tee.nonce", requestId, address(this), block.chainid)). An integrator reading the error doc to compute the canonical nonce would derive the wrong value and get rejected. Fix: add the 'tangle.tee.nonce' prefix to the @dev comment.
tangletools · 2026-05-04T20:12:34Z · trace
Summary
Workstream B + C from the Tangle Cloud app-store program plan:
AppRegistry.sol(greenfield) — on-chain home for blueprint apps: publisher namespaces, verified flag, per-app metadata pointers with strict version monotonicity, full version history, two-step namespace transfer, governance-only verify/revoke. UUPS-upgradeable + AccessControl.OperatorSecurityCommitment— operators commit to a specific TEE backend + measurement + nonce-binding at approval time; the live attestation produced when the workload provisions can be cross-checked against the on-chain commitment.DirectTdxis rejected at approval (mirror ofai-agent-sandbox-blueprintPR Update MSBM w/ new features in Tangle (same branch name) #50 policy).Builds on the security primitives PR #115 merged earlier today (
MBSMRegistry, BLS commitment paths inServicesApprovals,OperatorSecurityCommitmenttypes).Workstream B — AppRegistry
Storage
mapping(bytes32 => address) _namespaceOwner— namespace → owning publisher.mapping(bytes32 => bool) _namespaceVerified— governance-set verification flag.mapping(bytes32 => NamespaceTransfer) _pendingTransfers— two-step transfer state.mapping(bytes32 => AppRecord) _apps— per-app record.mapping(bytes32 => AppMetadataPointer[]) _appHistory— every version ever pinned, in order.mapping(bytes32 => EnumerableSet.Bytes32Set) _namespaceApps— O(1) listing per namespace.uint256[44] private __gap— reserved for facet-style upgrades.Surface
claimNamespace(bytes32)— first-claimer wins.transferNamespace(bytes32, address)/acceptNamespace(bytes32)— two-step with stale-proposal rejection (proposer must still own the namespace at accept time).publishApp(bytes32 namespace, bytes32 appId, uint64 blueprintId, AppMetadataPointer)— publisher-only; appId must be globally unique; version != 0; metadataHash != 0.pinAppVersion(bytes32 appId, AppMetadataPointer)— strictly monotonic version; previous pointer remains in history; refreshes verified mirror; updates frozenAt.setVerified(bytes32, bool),revokeApp(bytes32, string reason).getApp,getAppHistory,getAppFrozenAt,listApps(namespace),appCount(namespace),namespaceOwner,pendingNamespaceTransfer,isNamespaceVerified,appExists.Roles
DEFAULT_ADMIN_ROLEadministers the role hierarchy (timelock recommended).UPGRADER_ROLEauthorises UUPS upgrades.GOVERNANCE_ROLEflipsverifiedand revokes apps.Errors
Unauthorized,NamespaceTaken,AppExists,AppNotFound,MetadataHashMismatch,Revoked,InvalidNamespace,InvalidAppId,InvalidVersion,NoPendingTransfer,StaleTransferProposal.Tests (
test/registry/AppRegistryTest.t.sol, 23 cases)setVerifiedgovernance-only;verifiedflips throughgetApp.revokeAppblocks further pins; double-revoke reverts; non-governance revoke reverts.pinAppVersionrejects equal/lower versions; rejects zero hash.listApps+appCountvia EnumerableSet (insertion order, swap-pop on remove).Workstream C — TEE-attested commitments
Types (
src/libraries/Types.sol)Enum values are append-only by policy (storage hazard otherwise).
Approval entrypoint
Validates each TEE commitment (rejects
DirectTdx, rejects past expiries), records the per-operator array under_requestTeeCommitments[requestId][operator]and emitsTeeCommitmentRecorded. Activation copies the array onto_serviceTeeCommitments[serviceId][operator]viaTangleServicesFacet._persistTeeCommitments.Read surface
Returned array matches storage order. Empty if the operator approved without TEE commitments.
Storage layout impact
TangleStorage.__gapreduced 44 → 42 to accommodate the two new mappings:Errors
DirectTdxNotPermitted,TeeCommitmentExpired(uint64 expiresAt, uint64 nowTimestamp),MeasurementMismatch(bytes32 expected, bytes32 observed),TeeCommitmentLengthMismatch(uint256 commitmentCount, uint256 teeCommitmentCount).Tests (
test/tangle/TeeCommitmentApprovalTest.t.sol, 9 cases)getTeeCommitment.DirectTdxNotPermitted).TeeCommitmentExpired)._persistTeeCommitments.Test plan
forge fmton changed files only.forge build(in flight at push time on a 341-file recompile after the formatting pass; CI runs the canonical build + tests).forge test --match-path 'test/registry/*'andtest/tangle/TeeCommitment*.TeeBackend).Out of scope (deferred to follow-ups, captured in the program plan)
_handleProvisionResulthook inai-agent-sandbox-blueprint) does the verifier walk. Bringing those on-chain costs significant gas; tracked under Workstream C P2 in the plan.setBackendRootCa(backend, ca)governance call for verifier root rotation — needs the on-chain verifier first.Notes
This pairs with
ai-agent-sandbox-blueprint#55(Workstream E: Firecracker port mappings + auth-path GC). Together they round out the contract / runtime story for the app-store program; surface rendering + brand adoption is indapp#3164.