Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion .github/workflows/typescript-sdk-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ on:
description: Exact dispatched protected annotated sdk-vX.Y.Z tag/ref
required: true
type: string
publish_release:
description: Publish an immutable public release only after explicit approval of this SDK version
required: true
default: false
type: boolean
reconcile_only:
description: Verify and retain a receipt for an exact existing published release; never publish
required: true
Expand Down Expand Up @@ -293,7 +298,7 @@ jobs:
fi

publish:
name: Publish protected immutable SDK release
name: Qualify draft or explicitly publish protected SDK release
needs: [build, policy_preflight]
if: github.repository == '777genius/infinity-context'
runs-on: ubuntu-latest
Expand Down Expand Up @@ -366,6 +371,7 @@ jobs:
shell: bash
env:
GH_TOKEN: ${{ github.token }}
PUBLISH_RELEASE: ${{ inputs.publish_release }}
RECONCILE_ONLY: ${{ inputs.reconcile_only }}
RELEASE_COMMIT: ${{ needs.build.outputs.commit }}
RELEASE_TAG: ${{ needs.build.outputs.tag }}
Expand All @@ -378,6 +384,7 @@ jobs:
bash packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh

- name: Create non-release verification receipt
if: steps.publish.outputs.release_state == 'published'
shell: bash
env:
RELEASE_TAG: ${{ needs.build.outputs.tag }}
Expand All @@ -394,6 +401,7 @@ jobs:
--tag "${RELEASE_TAG}"

- name: Retain verification receipt for Discord and operations custody
if: steps.publish.outputs.release_state == 'published'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: typescript-sdk-release-verification-${{ needs.build.outputs.tag }}
Expand All @@ -402,3 +410,14 @@ jobs:
include-hidden-files: false
overwrite: false
retention-days: 90

- name: Retain mutable draft qualification evidence
if: steps.publish.outputs.release_state == 'draft'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: typescript-sdk-draft-qualification-${{ needs.build.outputs.tag }}
path: draft-qualification/infinity-context-sdk-draft-qualification-receipt.json
if-no-files-found: error
include-hidden-files: false
overwrite: false
retention-days: 90
37 changes: 35 additions & 2 deletions docs/typescript-sdk-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ the 0.2.3 assets or move its tag to distribute this fix.

Version 0.2.4 is prepared but not yet published, and no consumer is pinned to its
release asset. The commands below apply only after the reviewed source is committed
and the release operator is ready to create the new immutable release.
and the release operator is ready to qualify a draft. Public publication requires
explicit approval of the exact SDK version and `publish_release=true`.

Start from the reviewed release commit. Create and push the one protected annotated
tag; the workflow never creates or moves it:
Expand All @@ -125,6 +126,7 @@ gh workflow run .github/workflows/typescript-sdk-release.yml \
--repo 777genius/infinity-context \
--ref sdk-v0.2.4 \
-f sdk_tag=sdk-v0.2.4 \
-f publish_release=false \
-f reconcile_only=false
gh run list --repo 777genius/infinity-context \
--workflow .github/workflows/typescript-sdk-release.yml --limit 1
Expand Down Expand Up @@ -158,7 +160,7 @@ non-resumable. An exact published release follows a read-only reconciliation pat
Immediately before draft creation and publication, the helper revalidates the exact
annotated tag object, commit, and active creation/update/deletion ruleset. It creates
one draft, uploads without `--clobber`, downloads and compares both assets, attempts
publication once, and then reconciles the server state for at most six observations
publication once only with `publish_release=true`, and then reconciles the server state for at most six observations
even when `gh release edit` reports failure. It never recreates or reuploads during
reconciliation.

Expand All @@ -181,8 +183,39 @@ fresh pack-once build, semantically verifies the released manifest using its ori
run ID/attempt, rechecks tag/ruleset state, and requires the release to be published,
immutable, and structurally exact.

## Draft qualification and explicit publication

`publish_release` is a boolean input defaulting to `false`. Omission or `false`
never promotes a release. The helper accepts only exact `true` or `false` strings;
an unset environment value defaults to false, while an empty or invalid value fails
before any API call. Set `publish_release=true` only after explicit approval of the
exact version. This is operator authorization, not an independent reviewer gate.

Draft mode keeps all build, protected-tag, immutable-policy, transport and exact
asset checks. It creates one draft, uploads the two assets without overwrite,
downloads and byte-compares both, and rechecks draft identity before recording
`infinity-context-sdk-draft-qualification-receipt.json`. The separate 90-day Actions
artifact is named `typescript-sdk-draft-qualification-sdk-vX.Y.Z`. It records the
observed draft URL/ID, asset IDs, downloaded hashes and sizes, source/tag/workflow
identity and run. It is a mutable observation, never proof of public availability
or immutable attestation. Drafts can change after observation. No release or asset
attestation command runs for a draft; the published receipt verifier remains strict.
Export this artifact into operations custody before expiry.

Existing drafts remain non-resumable in every mode, including explicit publication
and reconcile-only. This change does not provide trusted same-byte draft promotion:
redispatch with `publish_release=true` cannot approve/promote an existing draft.
Investigate and prepare a new reviewed patch version through the release process;
never overwrite draft assets or move its tag. No version bump is made by this change.
An existing published release still follows the protected read-only reconciliation
path even when publication is false. Reconcile-only always forbids mutations and
rejects absent or draft releases, regardless of `publish_release`.

## Download, verify, and cold install

The following instructions apply only after explicitly approved public publication.


The immutable release URL is a dependency: do not use a branch archive or Actions
artifact as distribution. Download and verify the exact two assets:

Expand Down
4 changes: 2 additions & 2 deletions packages/infinity_context_ts_sdk/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/infinity_context_ts_sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@infinity-context/sdk",
"version": "0.2.4",
"version": "0.3.0",
"description": "Production-grade TypeScript SDK for the Infinity Context memory API.",
"license": "Apache-2.0",
"repository": {
Expand Down
51 changes: 50 additions & 1 deletion packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ for name in GITHUB_OUTPUT GITHUB_REPOSITORY GITHUB_RUN_ATTEMPT GITHUB_RUN_ID \
required_env "${name}"
done

PUBLISH_RELEASE="${PUBLISH_RELEASE-false}"
case "${PUBLISH_RELEASE}" in
true|false) ;;
*) die "PUBLISH_RELEASE must be true or false" ;;
esac

case "${RECONCILE_ONLY:-false}" in
true|false) ;;
*) die "RECONCILE_ONLY must be true or false" ;;
Expand Down Expand Up @@ -202,7 +208,7 @@ if [ "${RELEASE_STATE}" = published ]; then
--workflow-run-attempt "${origin_run_attempt}" \
--workflow-run-id "${origin_run_id}" \
--workflow-sha256 "${WORKFLOW_SHA256}"
else
elif [ "${PUBLISH_RELEASE}" = true ]; then
revalidate_tag_and_ruleset
set +e
gh release edit "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --draft=false
Expand All @@ -226,6 +232,48 @@ else
done
fi

# Compare identity/content only: downloads can change API counters and asset order.
draft_identity() {
jq -Sc '{id, tag_name, name, draft, prerelease, html_url,
assets: ([.assets[] | {id, name, size, digest}] | sort_by(.id, .name))}'
}

# Mutable draft observations are not immutable/public attestations.
if [ "${RELEASE_STATE}" = draft ]; then
observed_release="$(draft_identity <<<"${RELEASE_JSON}")"
revalidate_tag_and_ruleset
inspect_release
[ "${RELEASE_STATE}" = draft ] || die "Qualification release no longer draft"
validate_release_shape true
test "$(draft_identity <<<"${RELEASE_JSON}")" = "${observed_release}" || \
die "Draft identity or assets changed during qualification"
mkdir draft-qualification
asset_evidence='[]'
for asset in "${assets[@]}"; do
digest="$(sha256sum "verification-receipt/${asset}" | cut -d' ' -f1)"
byte_length="$(wc -c <"verification-receipt/${asset}")"
asset_evidence="$(jq --arg name "${asset}" --arg sha256 "${digest}" \
--argjson byte_length "${byte_length}" \
'. + [{name: $name, sha256: $sha256, byte_length: $byte_length}]' <<<"${asset_evidence}")"
done
jq -n --argjson release "${RELEASE_JSON}" --argjson assets "${asset_evidence}" \
--arg repository "${GITHUB_REPOSITORY}" --arg commit "${RELEASE_COMMIT}" \
--arg tag_object "${TAG_OBJECT}" --arg workflow_sha256 "${WORKFLOW_SHA256}" \
--arg run_id "${GITHUB_RUN_ID}" --arg run_attempt "${GITHUB_RUN_ATTEMPT}" '
{schema_version: "infinity-context-typescript-sdk-draft-qualification.v1",
release_state: "draft", immutable_attestation_verified: false,
evidence_scope: "Observed mutable draft; downloaded bytes match this build. Not public distribution or immutable attestation.",
repository: $repository, source_commit: $commit, tag_object: $tag_object,
workflow_sha256: $workflow_sha256, run_id: $run_id, run_attempt: $run_attempt,
release_id: $release.id, release_tag: $release.tag_name,
observed_draft_url: $release.html_url,
assets: [$assets[] as $asset | $asset +
{id: ($release.assets[] | select(.name == $asset.name) | .id)}]}
' >draft-qualification/infinity-context-sdk-draft-qualification-receipt.json
printf 'release_state=draft\n' >>"${GITHUB_OUTPUT}"
exit 0
fi

printf '%s\n' "${RELEASE_JSON}" >verification-receipt/release.json
release_attestation="$(gh release verify "${RELEASE_TAG}" \
--repo "${GITHUB_REPOSITORY}" --format json)"
Expand All @@ -240,3 +288,4 @@ for asset in "${assets[@]}"; do
>"verification-receipt/${asset}.attestation.json"
done
printf 'url=%s\n' "$(jq -er '.html_url' <<<"${RELEASE_JSON}")" >>"${GITHUB_OUTPUT}"
printf 'release_state=published\n' >>"${GITHUB_OUTPUT}"
Loading