Skip to content

revertCommit's soft-remove limb hard-codes intent: 'override-artifact', so a commit that CREATED an object cannot be reverted either #6620

Description

@baozhoutao

Found while implementing #6563 (the RESTORE half of the same loop). Recording only, per Prime Directive #10 — a different line with a different cause (a hard-coded literal, not an unstated default), and outside #6563's ruled file surface.

Evidence

ObjectStackProtocolImplementation.revertCommit (packages/metadata-protocol/src/protocol.ts) has two limbs. #6563 fixes the existedBefore one. The OTHER limb — an artifact the commit CREATED, which the revert soft-removes — states its intent as a constant:

if (!it.existedBefore) {
    // Created by this commit -> soft-remove (metadata only; table stays).
    if (current) {
        await repo.delete(ref, {
            parentVersion: current.hash,
            actor,
            source: 'protocol.revertCommit',
            intent: 'override-artifact',
            state: 'active',
        });
    }
    reverted.push({ type: it.type, name: it.name, action: 'removed' });
}

SysMetadataRepository.delete opens with this.assertAllowed(ref.type, opts.intent) — the same gate put uses — which refuses any type whose registry entry is not allowOrgOverride. object is exactly such a type, so a commit that created an object cannot be undone.

Measured on origin/main @ e39dd66 against a real SysMetadataRepository over an in-memory sys_metadata / sys_metadata_history / sys_metadata_commit double (the #6215 harness in packages/objectql/src/protocol-commit-history.test.ts): an object created once through saveMetaItem under a Studio package workspace, recorded in a commit as existedBefore: false, then reverted through revertCommit:

{
 "success": false, "revertedCount": 0, "failedCount": 1, "reverted": [],
 "failed": [{ "type": "object", "name": "myapp_invoice",
   "error": "[NOT_OVERRIDABLE] 'object' is not allowOrgOverride in the registry. ...",
   "code": "NOT_OVERRIDABLE" }]
}

…and the sys_metadata row is still there afterwards (1 row remaining). The same measurement re-run on #6563's branch is byte-identical: fixing the restore limb does not touch this one.

Why it matters

This is the first-build revert — the AI/Studio flow that publishes a brand-new app and then undoes it. Every object the commit created stays behind, success is false with a populated failed[], and the package is left half-reverted (its overlay-allowed items removed, its objects not). rollbackToPackageCommit reverts through the same loop, so it inherits this.

Suggested direction (not a ruling)

Same shape as #6563's ruling, applied to the other limb: derive the intent from the artifact per item (isArtifactBacked -> 'override-artifact' : 'runtime-only'), exactly as the sibling caller rollbackMetaItem does, rather than stating a constant. A genuinely artifact-backed item must still be refused. The repository's gate is right; the caller is what never says which of the two cases it is.

Note the delete-limb equivalent of #6563's premise check is worth re-running before implementing: deleteMetaItem (the per-item delete path) should be read first to see which intent it derives, so the two delete callers agree.

Related: #6563 (the restore limb, same loop), ADR-0067, ADR-0005.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions