Repository navigation
Conversation
PausableView no longer carries a pauser. The core interface reports the flag and grants no authority, so a template can implement it whatever authorizes its switch. PausableAdmin requires Pausable and owns the switch for the single-party case: setPausedImpl, PausableAdmin_Pause, PausableAdmin_Unpause. The compiler rejects an implementation that omits the core. A single Party cannot express a role grant, an M-of-N approval, or a timelock, and a Daml controller expression is pure, so it cannot fetch a credential to decide who may act. Freezing pauser into the shared view would have forced those consumers to publish a false claim about who holds the switch, and to ship a live second path to it. They now implement Pausable alone and write their own pause choice. Adds a RoleVault fixture and a test covering that path.
Answers the six open questions in place, each beside the question it settles, and fills Dev Notes with the three decisions that depart from the Recommendation: the view carries paused only, the guards do not evaluate ledger time, and the separate-contract shape is not shipped. Also notes the PausableAdmin split, which the research did not anticipate, and the controller-purity constraint behind it. The Recommendation is left as written. It records what the research concluded, not what was later chosen.
Drop PausableAdmin, PausableAdminView, PausableAdmin_Pause, and PausableAdmin_Unpause. Pausable now carries the flag, the getter, setPausedImpl, and the new top-level pause/unpause, and it names no pause authority. Every consumer writes the choice that decides who may flip the switch, matching _pause() and _unpause() being internal in Solidity. The add-on saved a single-party consumer four lines and cost a pauser field in a frozen view, where a placeholder value publishes a false claim about who holds the switch. Moving setPausedImpl onto the core interface makes pause/unpause carry the guard for every authority model rather than only the single-party one. Two hazards the add-on used to absorb are now documented on pause, on the interface, and in the README: the calling choice must be consuming, since pause creates the successor but archives nothing; and setPausedImpl is a public method whose create needs the implementing template's signatory authority, which is what stops an unrelated contract from flipping the flag. Test fixtures show both authority models as consumer-written choices, and two new tests pin the archival behaviour and the authority limit.
openzeppelin-pausable-v1 and its test package are superseded by openzeppelin-pausable-api-v1 under packages/. Drop both from the workspace and from every reference to them: the multi-package package list, the component tables in the root and experiments READMEs, the root repository-layout block, their CHANGELOG section, and their lint and test commands in the AGENTS.md validation list. The research artifact's Out of Scope bullet named both prototypes by path; it now records that they were removed and superseded. Also add packages/security/pausable-api-v1 and test/pausable-api-v1 to the AGENTS.md validation list, which never listed them. The CI discovery scripts already cover both.
Stage 2 of the pipeline, built on artifacts/01-research.md and produced greenfield: the prototype packages already on disk were not treated as a baseline. The package is a shareable component - one frozen interface, a one-field view, and pure guards and guarded flips, with no templates. The flag lives on the consumer's own template, so the guard reads the contract the gated choice is already exercising and cannot be handed a substituted or omitted switch. Records fifteen design decisions with their reasoning, including the two alternatives that were evaluated and rejected: pausing by archiving the contract and restoring it on unpause, and a type class for the write path in place of an interface method. Every shape discussed was compiled before being chosen or dropped.
Interface surface: - setPausedImpl becomes a pure setPaused : Bool -> Pausable. The flips now create the successor, so the method archives nothing and returns a value. - Drop the Pausable_Paused read choice. Its flexible controller made it a self-divulgence primitive - informees of an exercise node see the input contract - frozen into every adopter and unremovable. isPaused answers the same question purely, and an off-ledger reader uses the interface view. - pause and unpause return ContractId t instead of ContractId Pausable, so no coercion appears at the consumer's call site. - Add pauseWith and unpauseWith, so a registry can set its own pauseInfo fields in the same transaction as the flip instead of bypassing the guard. - Add eFlagNotApplied, asserted after both steps, so an implementation that ignores the flag argument or a lambda that clobbers it fails loudly rather than yielding a pause that silently does not pause. - Add eImplementerTypeMismatch, reported through fromSomeNote, for an implementation that returns another implementer's template. Fourteen exports. Because pause now needs HasCreate on a concrete template, a stranger holding a ContractId Pausable can no longer even compile a flip; that path previously compiled and failed at runtime for want of authority. Reads stay generic: isPaused and whenNotPaused both accept a bare Pausable. Tests cover the new surface with fixtures that break the setPaused law on purpose, a nonconsuming flip that demonstrates the one obligation the library cannot enforce, and the sibling-field flips. All 21 scripts pass.
The design decisions log enumerated the three shapes that were built and compiled as (A), (B), (D), skipping (C). Rename the third to (C) and update the three later references to it in decisions 6 and 7.
Two standalone projects under examples/pausable/, each integrating the released openzeppelin-pausable-api-v1 DAR through data-dependencies and carrying a Daml Script that runs its lifecycle. They are the worked examples from the design artifact, made executable. vault is the minimal adoption: the three-line interface instance, whenNotPaused on a gated choice, whenPaused on a recovery path, an unguarded escape hatch so a pause does not trap the owner's funds, and the two choices that name the pause authority. Its script walks the withdrawal, the pause, the refused withdrawal, the refused double pause, the recovery path, and the return to normal operation. registry is the sibling-field case: pauseWith and unpauseWith record and clear CIP-0112 pauseInfo fields in the same transaction as the flip, so the metadata response comes from one on-ledger contract while PausableView still carries the flag alone. scripts/check-examples.sh discovers every example, verifies it data-depends on a production DAR, and runs its script; ci.yml calls it after the lint step, so the examples are integration evidence rather than untested prose. Directories under examples/ group by component and never appear in a package name, matching the convention packages/ and experiments/ already follow.
The README still described the pre-design prototype. It listed Pausable_Paused and setPausedImpl, neither of which exists, and its interface instance showed setPausedImpl as an Update action creating a contract rather than the pure setPaused. Both example choices returned ContractId Pausable, which does not type check against pause now that the flips return the consumer's own template id. pauseWith, unpauseWith, and the four exported error constants were missing entirely. Also state why the interface declares no choice and how a reader gets the flag without one, and split the field-preservation caveat: the flag is checked and a flip that fails to apply it raises eFlagNotApplied, while the other fields are not checked and cannot be. Link the two consumer examples.
`submitWithDisclosures` and `submitWithDisclosuresMustFail` are legacy Daml Script API. Build the disclosure into `SubmitOptions` with `actAs` and `disclose` instead.
The lambda-taking flip forced consumers to wrap a record update in a function. markPaused and markUnpaused run the guard and the flip and hand back the template value, so a choice writes an ordinary create with the sibling fields set, with no intermediate contract.
A leading doc comment on an interface method is a parse error for the docs tool, so the module produced no API documentation. The setPaused documentation is now a trailing comment, which the tool renders.
Vault_RedeemToAdmin zeroed the balance and moved nothing, which contradicted the documentation calling it an escape hatch for the owner's funds. Vault_Redeem and Vault_EmergencyDrain now create a Payout to the owner and to the admin respectively.
Open cleanup lets any party choose a ledger time at expiresAt while the record time is up to the tolerance T earlier. The Time on Canton section, the TimelockConfig field comment, and the caveats state that the window before the earliest cleanup is at least gracePeriod - T, and that gracePeriod must be well above T. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An operation keeps the schedule it received, so a stricter policy does not reach it, and executors choose the order of ready operations. The README states both, and a test shows an operation executing at its original readyAt after minDelay rises to 30 days. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…limits A test runs Timelock_Apply and isReadyAt at both edges of readyAt and expiresAt, so the frozen copies and the public guards cannot drift apart. Another test shows that a zero-delay policy makes the next operation immediate. The READMEs state that the frozen copies cannot take a fix, what ePendingMismatch can and cannot detect, and that the pending list has no size bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ericnordelo
reviewed
Sep 23, 2026
## Summary This PR applies the findings of an independent two-model review (Claude and Codex) of #46 at ffd9093. It targets `pausable-proposal`, so it can merge into #46 as one unit. Each finding is one commit, and each finding has an inline comment on #46 that links to its commit. Neither production package changes behavior. The changes are documentation, one new example, example fixes, and tests. Two commits change doc comments in `OpenZeppelin.Api.PausableV1` (ce25c47, 343e374), which changes the source of the frozen package before its release. ## Changes | Finding | Severity | Commit | Change | |---|---|---|---| | F1 | Medium | ce25c47 | The API module header and both READMEs state that the signatories can reset the flag by archive and create, outside the flip choice. A test shows the reset. | | F2 | Medium | d63b4f7 | The API README and `ARCHITECTURE.md` name `-Wno-template-has-new-interface-instance`, which the SCU check needs for a retrofit. A new retrofit example builds versions 1.0.0 and 1.1.0 of one package, with tests. | | F3 | Low | 60ea95a | The API README describes old package versions after a retrofit. A test shows that an exercise of the old template runs the new version. | | F4 | Low | 236cb42 | The `pausable-v1` README states that a guard fix reaches only new consumer versions. | | F5 | Low | 343e374 | The `Pausable` doc comment lists the items that `AGENTS.md` requires. The README describes the implicit `Archive` choice. A test covers it. | | F6 | Low | 71b34c4 | The docs state that the guard runs before the state change and that the first failing check sets the error. A test covers the credential case. | | F7 | Low | 2ded6f5 | The API README describes the pause interval across successor contracts. | | F8 | Info | 18b7cd6 | The vetting statements cover every participant that vets the consumer package. | | F9 | Info | fc32b19 | The registry example adds `Registry_UpdatePauseInfo`, guarded with `whenPaused`. | | F10 | Low | 5d65b5b | The root README lists the three experimental components. | | F11 | Info | afeadef | The `pausable-v1` README states that `.Internal` is not public API. | | F12 | Info | 21cc9ba | The migration docs state that the migration copies the flag. | | F13 | Info | 5308b5f | `Vault_Withdraw` pays the amount to the owner as a `Payout`. | | Tests | | 2628f8b | Flag persistence through redemption and drain, example authority, the guard order on an empty entry, repeated flips, a nonconsuming flip that archives `self`, a nonconsuming gated choice, and guards on an interface value. | ## Verification ```sh dpm build --all scripts/check.sh scripts/check-lint.sh scripts/check-examples.sh scripts/check-coverage.sh ``` All pass locally on SDK 3.4.11. Test counts: `test/api-pausable-v1` 5, `test/pausable-v1` 26, `vault-test` 14, `registry-test` 12, `retrofit-test` 3. ## Caveats - F3: in Daml Script, an exercise that names the 1.0.0 template runs version 1.1.0, so the guard applies. The downgrade refusal for a submission that pins 1.0.0 through package preference comes from the reviewer's test and from the SCU rules. This PR has no test for it, because Daml Script needs the package ID of 1.0.0 for that. - `examples/pausable/retrofit-v1-0` data-depends on `openzeppelin-api-pausable-v1` without using it, because `scripts/check-examples.sh` requires a production DAR. The package sets `-Wno-unused-dependency` for that reason. - There is no Ledger API test of the `DAML_FAILURE` error id. `scripts/check-sandbox.sh` covers only the token package. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Nenad <xinef.it@gmail.com> Signed-off-by: Pepe Blasco <pepeblascondc@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Nenad <nenad.misic@openzeppelin.com> Co-authored-by: Nenad <xinef.it@gmail.com>
…package Timelock_Apply and Timelock_Drop now call the applyImpl and dropImpl methods. The consumer implements them with applyOperation and dropOperation from OpenZeppelin.TimelockV1, which run the checks that the frozen choice bodies ran. A fix to a check is now a new version of the function package, and a consumer picks it up through SCU. The failure statuses and the private checks live in OpenZeppelin.TimelockV1.Internal, and the public module re-exports the failure statuses. The duplicate guards and the test that pinned them to each other are removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anifests in check.sh
raynaudoe
reviewed
Sep 28, 2026
Timelock_Apply and Timelock_Drop are nonconsuming, and applyOperation and dropOperation archive the timelock before they call apply or unschedule. A consuming choice informs the timelock's observers of every action in apply. The observers now see only the archive and the successor.
0xNeshi
reviewed
Sep 29, 2026
raynaudoe
reviewed
Sep 29, 2026
raynaudoe
reviewed
Sep 29, 2026
raynaudoe
approved these changes
Sep 29, 2026
… its check The timelock-v1 README states that a submission can select the old version of the consumer package while it stays vetted, so a security fix needs the old version unvetted. The treasury example test makes admin an observer of the foreign config, so the fetch succeeds and the signatory check refuses the config.
**This is just a partial review of #47 .** ## Changes ### Cancellers do not bound direct archives - `packages/security/api-timelock-v1/README.md` now states that `cancellers` does not limit who can remove an operation, because its signatories can always call `Archive` directly. It also says to use the timelock's full signatory set if no single signatory may remove an operation alone. *Why:* Daml always gives signatories an `Archive` choice, so integrators need to know about it and size the signatory set themselves. ### `ARCHITECTURE.md` - Moves the "choice body calls a method that calls a library function" rationale out of the Timelock section into a general section, with a link to the Splice `TransferFactory_Transfer` example and a pointer to `Checked`. *Why:* the pattern applies to any component with interfaces, not only Timelock. - Removes Timelock paragraphs that repeated package `README.md` content: package contents, lifecycle authority, and the three-contract integration layout. *Why:* `ARCHITECTURE.md` holds design rationale. Usage and behavior belong in the package `README.md` files. - Drops the Pausable and Timelock state examples from the migration paragraph and formats `Pausable` and `pause` as code. *Why:* the migration paragraph applies to any contract state, so the component-specific examples were not needed.
Rename the Timelock test packages to the -test directory layout, move the Timelock packages to SDK 3.5.8, and add the treasury example README and an archive test that the example coverage gate requires.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #31
Summary
Adds the Timelock component as two packages, split as Pausable is:
openzeppelin-api-timelock-v1, moduleOpenZeppelin.Api.TimelockV1: the frozen interfacesTimelock,Operation, andTimelocked, their views,TimelockConfig,Pending, andDropReason. The package defines no templates and holds no logic.openzeppelin-timelock-v1, moduleOpenZeppelin.TimelockV1: the lifecycle functions, the schedule functions, the policy validation, the pending-list functions, the time guards, and the failure statuses. It upgrades independently of the frozen package.The consumer writes the timelock template, one template for each operation kind, and the schedule choices. The timelock implements
Timelock. It holds the delay policy and the pending list, and itsapplymethod dispatches on the operation's template withfromInterface. The interface choicesTimelock_ApplyandTimelock_Dropcall theapplyImplanddropImplmethods, which the consumer implements with one call toapplyOperationordropOperation. These functions check pending membership, shared authority, executor or canceller permission, and the ledger-time bounds. Then they archive the operation and call the consumer's method. The Splice token standard uses the same pattern, so a fix to a check ships in the function package and reaches consumers through SCU.Design page: Timelock (Design & Development specs, with the research report and the design document as child pages).
Changes
packages/security/api-timelock-v1: the interfaces, the views, theapplyImplanddropImplmethods, andTimelock_Apply,Timelock_Drop,Operation_Execute,Operation_Cancel, andOperation_Cleanup.packages/security/timelock-v1:applyOperation,dropOperation,scheduleAt,scheduleAfter,isValidConfig,requireValidConfig,addPending,takePending,requireReady,requireExpired,isReadyAt,isExpiredAt, and ten failure statuses underopenzeppelin.com/timelock-. The failure statuses and the private checks live inOpenZeppelin.TimelockV1.Internal, and the public module re-exports the failure statuses.test/api-timelock-v1-test(49 Daml Script tests): the lifecycle, the pending list, direct calls, false targets, methods that return a wrong pending list, operations with extra signatories, and policy changes.test/timelock-v1-test(16 tests): the schedule functions, the policy validation, the pending-list functions, and the guards.examples/timelock/treasury: a timelock, a governed config, and a treasury on separate contracts. Payments do not contend with governance. The example has a proposer, an executor, cancellation, expiry cleanup, a recovery choice for stale entries, and self-administration of the delay policy.README.md,packages/README.md,examples/README.md,ARCHITECTURE.md,AGENTS.md,CONTRIBUTING.md,CHANGELOG.md, andmulti-package.yaml.Canton design points
applyreads its parameters through interface dispatch.isLedgerTimeGEandisLedgerTimeLT, notgetTime, so an apply transaction prepared beforereadyAtstays valid once it passes.Security review
Two independent reviews ran against 6601abe. Neither found a critical or high issue in the library. These commits address their findings:
ePendingMismatch).eInvalidAuthority).Treasury_Paycompares the full signatory sets, and the docs state that the delay does not bind a sole signatory.gracePeriodtolerance bound, package upgrades, and policy-change scope, plus tests.Verification
All pass locally on SDK 3.5.8.
Open for review
scheduleAfter, the one export that readsgetTime?minSetbackprotection in the library, or leave it to the consumer?gracePeriod?🤖 Generated with Claude Code