Skip to content

feat(intake): non-binary archive lane and provisional evidence tier - #103

Open
tonythethompson wants to merge 4 commits into
mainfrom
2-issues-85-automate-non-binary-archive-intake-lane-p2-2
Open

feat(intake): non-binary archive lane and provisional evidence tier#103
tonythethompson wants to merge 4 commits into
mainfrom
2-issues-85-automate-non-binary-archive-intake-lane-p2-2

Conversation

@tonythethompson

@tonythethompson tonythethompson commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Adds the P2 non-binary archive intake lane (#85) and the P6 provisional evidence tier (#86).

#86 — provisional evidence tier in gen_spec.py

build_spec gained keyword-only provisional / deferral_reason, surfaced as --provisional / --deferral-reason. A provisional spec omits verified_with entirely and emits evidence_tier: "provisional" plus a stripped deferral_reason in its place — numan-registry's add-package.py aborts when the key is merely present under --provisional, and its validate_spec only waives the lifecycle-evidence check when the key is absent.

Three guards reject a blank or missing reason, a reason passed without --provisional, and --provisional against a manifest entry that already has lifecycle evidence.

#85 — non-binary archive intake lane

New scripts/intake_archive.py resolves a ref to its immutable 40-char SHA, shallow-clones, verifies the declared entry file, builds a deterministic .tar.gz with package_plugin.py's parameters (sorted entries, fixed mtime, gzip mtime=0), emits an artifact.kind: archive spec, upserts manifest-archives.json, and prints an ARCHIVED TSV handoff line. It publishes nothing, so it stays hermetically testable.

New .github/workflows/intake-archive.yml publishes through the repo's audited ensure_release_absent.py + release_transaction.py claim → upload → finalize flow (cleanup on failure) rather than an inline gh release create. Between the two jobs, a gate re-hashes the downloaded artifact against the digest the archive job computed and refuses to proceed unless dist holds exactly the one expected file — nothing is claimed or published when it fires.

Top-level permissions: contents: read; only the publish job escalates to contents: write.

Deviation from the plan

GitHub caps workflow_dispatch at 10 inputs and CI's actionlint step failed with the planned 14. The dispatch surface is now git_url, ref, entry, package (owner/name, split in bash), type, description, tags, nu_version, activation (kind[:import]), deferral_reason (non-blank implies provisional). The version override is no longer a dispatch input and always derives from the ref; the script still accepts --owner/--name/--version/--provisional/--activation-kind/--activation-import for local runs.

Tested

  • python -m unittest discover -s scripts -p "test_*.py"230 tests, OK (skipped=1: the archive-member exec-bit test, which skips on Windows and runs on CI's ubuntu-latest)
  • python -m compileall -q scripts exit 0
  • scripts/format_json.py --check and scripts/check_repo_consistency.py OK
  • actionlint 1.7.7 exit 0; shellcheck 0.10.0 clean over every run body, including at -S style with no exclusions
  • Gate and dispatch-parsing bash exercised across degenerate inputs (missing digest, empty dist, extra file, nested archive) — all fail closed

Two passes of semantic review; approved at v2. The first pass caught a blocking gap — the publish job did not re-hash the downloaded artifact — which 5f1874b fixed.

Not runnable locally: roadmap-drift.yml (its checker lives in the numan repo, so only CI can confirm the roadmap edit) and validate_manifest.py --verify-upstream (needs per-plugin network git; unaffected by this change).

Known follow-ups

  • gen_spec.py --provisional has no CI entry point. build.yml is a single-input matrix over many plugins while the tier is per package, so the natural home is a per-entry manifest.json field — out of scope here. The rationale and the manual procedure for the motivating nu_plugin_bigquery intake are recorded in docs/roadmap.md.
  • An invalid non-empty activation import half (nu-module:bogus) exits 2 with an argparse usage dump instead of the repo's FAIL: line, and a multi-colon input drops its middle segment. Both fail closed.
  • The workflow's own bash is covered by string-presence assertions plus actionlint/shellcheck, not by a semantic test.

Closes #85
Closes #86


Summary by cubic

Adds a manual-dispatch workflow that archives non-binary packages (modules, scripts, completions) from an upstream commit and publishes them as release assets, plus a provisional evidence tier in gen_spec.py for packages that build but can't run lifecycle-prove in CI.

Non-binary archive intake

  • New scripts/intake_archive.py resolves a ref to its commit SHA, shallow-clones, verifies the entry file, and builds a deterministic .tar.gz; it publishes nothing itself.
  • New .github/workflows/intake-archive.yml publishes through the existing ensure_release_absent.py and release_transaction.py claim→upload→finalize flow, cleaning up on failure.
  • The publish job re-hashes the downloaded artifact against the digest from the archive job and refuses to proceed unless dist holds exactly that one file.
  • Top-level permissions are read-only; only the publish job escalates to contents: write.
  • GitHub caps workflow_dispatch at 10 inputs, so the dispatch surface is collapsed to 9 fields; version always derives from the ref.
  • Intake records re-intake provenance in manifest-archives.json, uploaded as an artifact for a maintainer to commit (the workflow pushes no commit).

Provisional evidence tier

  • gen_spec.py gains --provisional / --deferral-reason; provisional specs omit verified_with and emit evidence_tier: "provisional" with a stripped deferral_reason.
  • A blank or missing reason is rejected, as is a reason without --provisional, and --provisional on an entry that already has lifecycle evidence.
  • Any intake with an activation is staged provisionally because lifecycle evidence can only come from proving the published asset after the release completes.

Closes #85 and #86.

Written for commit c6c700b. Summary will update on new commits.

Review in cubic

tonythethompson and others added 4 commits August 28, 2026 03:15
Add --provisional / --deferral-reason so the binary intake lane can ship a
spec for a plugin that builds but cannot run lifecycle-prove in CI (the
nu_plugin_bigquery case: it needs Google credentials).

A provisional spec carries evidence_tier + a non-blank deferral_reason and
omits verified_with entirely, because numan-registry's add-package.py aborts
when --provisional is paired with a spec that merely contains the key, and its
validate_spec only waives the lifecycle-evidence check when the key is absent.
build_spec raises ValueError (never SystemExit) for a blank reason, a reason
without --provisional, and --provisional on an entry that already has
verified_with evidence, so main() reports them as FAIL and returns 1.

Non-provisional output is unchanged byte-for-byte.

Refs #86

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modules, scripts, and completions need no compilation, so they take an archive
lane instead of the cross-compile matrix. scripts/intake_archive.py resolves an
upstream ref to its immutable commit, shallow-clones it, verifies the declared
entry file, builds a deterministic .tar.gz with package_plugin.py's parameters,
emits an add-package-compatible archive spec with an inline sha256, and records
re-intake provenance in manifest-archives.json.

.github/workflows/intake-archive.yml drives the lane on manual dispatch and
publishes through the existing release transaction (ensure_release_absent.py
plus release_transaction.py claim/upload/finalize/cleanup), so the script itself
publishes nothing and stays hermetically testable.

workflow_dispatch accepts at most 10 inputs, so the dispatch surface carries
owner/name as `package` and activation kind/import as `activation`, treats a
non-blank `deferral_reason` as provisional intake, and always derives the
version from the ref.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sing

Review response for the non-binary archive lane:

- the publish job re-hashes the collected artifact against the digest the
  archive job computed and refuses a dist that is not exactly the one
  expected asset, the gate build.yml has via verify_packaged_assets
- correct the inline artifact.sha256 rationale in the module docstring and
  docs/roadmap.md: add-package.py recomputes the hash from the download and
  ignores the spec value
- record why an activation always lands provisional: lifecycle evidence can
  only come from proving the published asset, which does not exist yet
- reject an empty owner/name half in the workflow, and validate owner, name
  and version in the script before they reach a release tag or filename
- treat a trailing-colon activation as having no import mode instead of
  failing argparse's choices with a usage dump
- build the archive beside its target and rename, so a failed write cannot
  leave a partial file that blocks the retry
- cover archive member mode normalization, partial-archive cleanup, the
  --repo slug path, and upload-by-claimed-release-id for the new lane
- note that gen_spec.py's provisional flags have no build.yml input by
  design, and where the evidence tier actually gets written

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The gate test asserted the digest check but not the mapfile / -ne 1
block, so deleting the count guard left the suite green while
release_transaction.py upload would publish any extra file that
reached dist. Scope the assertions to the gate step and pin the
enumeration, the count check and the name equality.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 28, 2026

Copy link
Copy Markdown

We've triggered an ultrareview automatically — This PR adds a new archive intake lane and a release-publishing workflow with elevated permissions, plus provisional spec logic; a subtle bug could publish a tampered artifact or produce an invalid registry entry, so a deeper review is warranted.. I'll post findings when complete.

An ultrareview is cubic's deepest review, catching hard-to-find bugs in the most critical PRs. It runs a longer, multi-pass analysis using cubic's most capable review models, and typically takes around 30 minutes. It consumes your team's reviewed-lines allowance at 3× the standard rate.

Automated ultrareviews are disabled by default. We triggered this run as part of your trial. Want cubic to do this for every high-risk PR? Enable auto-ultrareview in your settings.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultrareview completed in 10m 36s

8 issues found across 9 files

Confidence score: 2/5

  • scripts/intake_archive.py accepts malformed SemVer tags and explicit versions, allowing invalid releases to be created; parse both derived and explicit versions strictly before release creation.
  • scripts/intake_archive.py can emit activation metadata for invalid module kinds, non-.nu entries, or entries excluded from the archive, causing downstream installation to abort; validate the module and entry contracts before publishing.
  • scripts/intake_archive.py copies --nu-version constraints without validation, so invalid ranges may be treated as matching and produce packages with incorrect compatibility claims; reject unrecognized constraint syntax.
  • .github/workflows/intake-archive.yml mishandles malformed activation values, can execute unsafe deferral_reason content, and can overwrite concurrent intake results with stale manifest snapshots; strictly parse and shell/Markdown-escape handoff data, then merge concurrent manifest updates safely.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/intake_archive.py">

<violation number="1" location="scripts/intake_archive.py:209">
P2: When `--entry` is `.git/HEAD`, this check succeeds even though `sorted_files` excludes `.git`; the emitted archive lacks the declared entry and the downstream installer aborts at numan-cli/numan/src/install/transaction.rs:323-330. Validate the entry against archiveable, normalized paths before emitting the spec.</violation>

<violation number="2" location="scripts/intake_archive.py:293">
P1: Tags such as `v1.2.3-01` or `v1.2.3+foo..bar` match this regex and pass `VERSION_RE`, but they are invalid SemVer. Parse both derived and explicit versions strictly before creating the release, because the resulting spec can be rejected or make the signed index unreadable at numan-cli/numan/src/core/package.rs:103-109.</violation>

<violation number="3" location="scripts/intake_archive.py:326">
P2: When activation is requested for a script, an unknown kind, or a non-`.nu` entry, this function still emits activation metadata. Validate `type == module`, `kind == nu-module`, and the `.nu` entry contract before publishing, because the client cannot activate those combinations at numan-cli/numan/src/install/transaction.rs:374-382.</violation>

<violation number="4" location="scripts/intake_archive.py:409">
P2: `--nu-version` is copied verbatim, so `bad range` becomes a valid-looking spec. The client ignores unrecognized constraint tokens and returns true at numan-cli/numan/src/core/nu_version.rs:76-123, making the package appear compatible with every Nu; validate the accepted range grammar before emission.</violation>

<violation number="5" location="scripts/intake_archive.py:418">
P2: When `--activation-import` is supplied without `--activation-kind`, validation accepts it and this guard silently emits an install-only spec. Reject an import mode without its activation kind.</violation>
</file>

<file name=".github/workflows/intake-archive.yml">

<violation number="1" location=".github/workflows/intake-archive.yml:117">
P2: When `activation` contains more than one colon, this split silently drops or ignores a segment instead of rejecting malformed `kind[:import]` input. For example, `nu-module:all:` emits default `module` metadata instead of the requested `all`; reject multiple colons before splitting.</violation>

<violation number="2" location=".github/workflows/intake-archive.yml:304">
P2: Two intake runs for different packages can run concurrently, but each handoff contains a full stale `manifest-archives.json` snapshot. Committing one artifact over the file drops records from the other run; merge the artifact into the current provenance file instead of replacing it.</violation>

<violation number="3" location=".github/workflows/intake-archive.yml:307">
P2: When `deferral_reason` contains quotes, backticks, `$()` or a newline, this copy-paste handoff can break the command or execute command substitutions. Shell-escape the value and Markdown-escape the summary, or omit raw user text from the command.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with Cursor

Re-trigger cubic

Comment thread scripts/intake_archive.py
anything else falls back to the 0.1.0-<short-sha> convention the registry
already uses for branch-pinned script and completion entries.
"""
match = re.fullmatch(r"v?(\d+\.\d+\.\d+(?:[-+].+)?)", ref)

@cubic-dev-ai cubic-dev-ai Bot Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Tags such as v1.2.3-01 or v1.2.3+foo..bar match this regex and pass VERSION_RE, but they are invalid SemVer. Parse both derived and explicit versions strictly before creating the release, because the resulting spec can be rejected or make the signed index unreadable at numan-cli/numan/src/core/package.rs:103-109.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/intake_archive.py, line 293:

<comment>Tags such as `v1.2.3-01` or `v1.2.3+foo..bar` match this regex and pass `VERSION_RE`, but they are invalid SemVer. Parse both derived and explicit versions strictly before creating the release, because the resulting spec can be rejected or make the signed index unreadable at numan-cli/numan/src/core/package.rs:103-109.</comment>

<file context>
@@ -0,0 +1,604 @@
+    anything else falls back to the 0.1.0-<short-sha> convention the registry
+    already uses for branch-pinned script and completion entries.
+    """
+    match = re.fullmatch(r"v?(\d+\.\d+\.\d+(?:[-+].+)?)", ref)
+    if match:
+        return match.group(1)
</file context>
Fix with Cursor

Comment thread scripts/intake_archive.py
"sha256": sha256,
},
}
if activation_kind:

@cubic-dev-ai cubic-dev-ai Bot Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When --activation-import is supplied without --activation-kind, validation accepts it and this guard silently emits an install-only spec. Reject an import mode without its activation kind.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/intake_archive.py, line 418:

<comment>When `--activation-import` is supplied without `--activation-kind`, validation accepts it and this guard silently emits an install-only spec. Reject an import mode without its activation kind.</comment>

<file context>
@@ -0,0 +1,604 @@
+            "sha256": sha256,
+        },
+    }
+    if activation_kind:
+        activation = {"kind": activation_kind}
+        if activation_import:
</file context>
Fix with Cursor

Comment thread scripts/intake_archive.py
reason is given without provisional intake, or if a `mod.nu` entry
is activated with import mode 'module'.
"""
if activation_kind and not provisional:

@cubic-dev-ai cubic-dev-ai Bot Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When activation is requested for a script, an unknown kind, or a non-.nu entry, this function still emits activation metadata. Validate type == module, kind == nu-module, and the .nu entry contract before publishing, because the client cannot activate those combinations at numan-cli/numan/src/install/transaction.rs:374-382.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/intake_archive.py, line 326:

<comment>When activation is requested for a script, an unknown kind, or a non-`.nu` entry, this function still emits activation metadata. Validate `type == module`, `kind == nu-module`, and the `.nu` entry contract before publishing, because the client cannot activate those combinations at numan-cli/numan/src/install/transaction.rs:374-382.</comment>

<file context>
@@ -0,0 +1,604 @@
+            reason is given without provisional intake, or if a `mod.nu` entry
+            is activated with import mode 'module'.
+    """
+    if activation_kind and not provisional:
+        raise ValueError(
+            "an activation requires provisional intake (no lifecycle evidence "
</file context>
Fix with Cursor

Comment thread scripts/intake_archive.py
"type": pkg_type,
"tags": tags,
"version": version,
"nu_version": nu_version,

@cubic-dev-ai cubic-dev-ai Bot Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: --nu-version is copied verbatim, so bad range becomes a valid-looking spec. The client ignores unrecognized constraint tokens and returns true at numan-cli/numan/src/core/nu_version.rs:76-123, making the package appear compatible with every Nu; validate the accepted range grammar before emission.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/intake_archive.py, line 409:

<comment>`--nu-version` is copied verbatim, so `bad range` becomes a valid-looking spec. The client ignores unrecognized constraint tokens and returns true at numan-cli/numan/src/core/nu_version.rs:76-123, making the package appear compatible with every Nu; validate the accepted range grammar before emission.</comment>

<file context>
@@ -0,0 +1,604 @@
+        "type": pkg_type,
+        "tags": tags,
+        "version": version,
+        "nu_version": nu_version,
+        **evidence,
+        "artifact": {
</file context>
Fix with Cursor

Comment thread scripts/intake_archive.py
resolved.relative_to(src_dir.resolve())
except ValueError as exc:
raise ValueError(f"entry path escapes checkout: {entry}") from exc
if not resolved.is_file():

@cubic-dev-ai cubic-dev-ai Bot Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When --entry is .git/HEAD, this check succeeds even though sorted_files excludes .git; the emitted archive lacks the declared entry and the downstream installer aborts at numan-cli/numan/src/install/transaction.rs:323-330. Validate the entry against archiveable, normalized paths before emitting the spec.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/intake_archive.py, line 209:

<comment>When `--entry` is `.git/HEAD`, this check succeeds even though `sorted_files` excludes `.git`; the emitted archive lacks the declared entry and the downstream installer aborts at numan-cli/numan/src/install/transaction.rs:323-330. Validate the entry against archiveable, normalized paths before emitting the spec.</comment>

<file context>
@@ -0,0 +1,604 @@
+        resolved.relative_to(src_dir.resolve())
+    except ValueError as exc:
+        raise ValueError(f"entry path escapes checkout: {entry}") from exc
+    if not resolved.is_file():
+        raise ValueError(f"entry file not found in checkout: {entry}")
+    return resolved
</file context>
Fix with Cursor

echo "- Commit the \`manifest-archives\` artifact over \`manifest-archives.json\` to keep re-intake provenance; this run pushes no commit."
if [ -n "$DEFERRAL_REASON" ]; then
echo "- provisional intake, deferral reason: $DEFERRAL_REASON"
echo "- next, in numan-registry: \`python scripts/add-package.py --spec $spec --write --provisional --deferral-reason \"$DEFERRAL_REASON\"\`"

@cubic-dev-ai cubic-dev-ai Bot Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When deferral_reason contains quotes, backticks, $() or a newline, this copy-paste handoff can break the command or execute command substitutions. Shell-escape the value and Markdown-escape the summary, or omit raw user text from the command.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/intake-archive.yml, line 307:

<comment>When `deferral_reason` contains quotes, backticks, `$()` or a newline, this copy-paste handoff can break the command or execute command substitutions. Shell-escape the value and Markdown-escape the summary, or omit raw user text from the command.</comment>

<file context>
@@ -0,0 +1,333 @@
+            echo "- Commit the \`manifest-archives\` artifact over \`manifest-archives.json\` to keep re-intake provenance; this run pushes no commit."
+            if [ -n "$DEFERRAL_REASON" ]; then
+              echo "- provisional intake, deferral reason: $DEFERRAL_REASON"
+              echo "- next, in numan-registry: \`python scripts/add-package.py --spec $spec --write --provisional --deferral-reason \"$DEFERRAL_REASON\"\`"
+            else
+              echo "- provisional intake: no"
</file context>
Fix with Cursor

echo "- release tag: \`$TAG\`"
echo "- upstream commit: \`$RESOLVED_SHA\`"
echo "- archive sha256: \`$ARCHIVE_SHA256\`"
echo "- Commit the \`manifest-archives\` artifact over \`manifest-archives.json\` to keep re-intake provenance; this run pushes no commit."

@cubic-dev-ai cubic-dev-ai Bot Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Two intake runs for different packages can run concurrently, but each handoff contains a full stale manifest-archives.json snapshot. Committing one artifact over the file drops records from the other run; merge the artifact into the current provenance file instead of replacing it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/intake-archive.yml, line 304:

<comment>Two intake runs for different packages can run concurrently, but each handoff contains a full stale `manifest-archives.json` snapshot. Committing one artifact over the file drops records from the other run; merge the artifact into the current provenance file instead of replacing it.</comment>

<file context>
@@ -0,0 +1,333 @@
+            echo "- release tag: \`$TAG\`"
+            echo "- upstream commit: \`$RESOLVED_SHA\`"
+            echo "- archive sha256: \`$ARCHIVE_SHA256\`"
+            echo "- Commit the \`manifest-archives\` artifact over \`manifest-archives.json\` to keep re-intake provenance; this run pushes no commit."
+            if [ -n "$DEFERRAL_REASON" ]; then
+              echo "- provisional intake, deferral reason: $DEFERRAL_REASON"
</file context>
Fix with Cursor

flags=()
if [ -n "$ACTIVATION" ]; then
activation_kind="${ACTIVATION%%:*}"
activation_import="${ACTIVATION##*:}"

@cubic-dev-ai cubic-dev-ai Bot Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When activation contains more than one colon, this split silently drops or ignores a segment instead of rejecting malformed kind[:import] input. For example, nu-module:all: emits default module metadata instead of the requested all; reject multiple colons before splitting.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/intake-archive.yml, line 117:

<comment>When `activation` contains more than one colon, this split silently drops or ignores a segment instead of rejecting malformed `kind[:import]` input. For example, `nu-module:all:` emits default `module` metadata instead of the requested `all`; reject multiple colons before splitting.</comment>

<file context>
@@ -0,0 +1,333 @@
+          flags=()
+          if [ -n "$ACTIVATION" ]; then
+            activation_kind="${ACTIVATION%%:*}"
+            activation_import="${ACTIVATION##*:}"
+            if [ -z "$activation_kind" ]; then
+              echo "FAIL: activation must be kind[:import]: $ACTIVATION" >&2
</file context>
Suggested change
activation_import="${ACTIVATION##*:}"
if [[ "$ACTIVATION" == *:*:* ]]; then
echo "FAIL: activation must be kind[:import]: $ACTIVATION" >&2
exit 1
fi
activation_import="${ACTIVATION##*:}"
Fix with Cursor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add provisional evidence tier and deferral reason to schema Automate non-binary archive intake lane (P2)

1 participant