From ef1fbd25cac5a87f9762ab6b3330ffd2ff8cad17 Mon Sep 17 00:00:00 2001 From: iliya Date: Thu, 10 Sep 2026 00:24:31 +0000 Subject: [PATCH 1/3] fix(release): default SDK qualification to draft-only mode --- .github/workflows/typescript-sdk-release.yml | 21 +++- docs/typescript-sdk-release.md | 37 ++++++- .../scripts/sdk-release-publish.sh | 45 +++++++- .../test_typescript_sdk_release_workflow.py | 103 +++++++++++++++++- 4 files changed, 201 insertions(+), 5 deletions(-) diff --git a/.github/workflows/typescript-sdk-release.yml b/.github/workflows/typescript-sdk-release.yml index 3e3f1830..b5941c5c 100644 --- a/.github/workflows/typescript-sdk-release.yml +++ b/.github/workflows/typescript-sdk-release.yml @@ -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 @@ -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 @@ -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 }} @@ -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 }} @@ -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 }} @@ -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 diff --git a/docs/typescript-sdk-release.md b/docs/typescript-sdk-release.md index d8433b04..ab53e559 100644 --- a/docs/typescript-sdk-release.md +++ b/docs/typescript-sdk-release.md @@ -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: @@ -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 @@ -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. @@ -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: diff --git a/packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh b/packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh index 2b4bc281..88eefc0c 100755 --- a/packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh +++ b/packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh @@ -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" ;; @@ -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 @@ -226,6 +232,42 @@ else done fi +# Mutable draft observations are not immutable/public attestations. +if [ "${RELEASE_STATE}" = draft ]; then + observed_release="$(jq -Sc '{id, tag_name, name, draft, prerelease, html_url, assets}' <<<"${RELEASE_JSON}")" + revalidate_tag_and_ruleset + inspect_release + [ "${RELEASE_STATE}" = draft ] || die "Qualification release no longer draft" + validate_release_shape true + test "$(jq -Sc '{id, tag_name, name, draft, prerelease, html_url, assets}' <<<"${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)" @@ -240,3 +282,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}" diff --git a/tests/unit/test_typescript_sdk_release_workflow.py b/tests/unit/test_typescript_sdk_release_workflow.py index 87ab7da2..21faaf4c 100644 --- a/tests/unit/test_typescript_sdk_release_workflow.py +++ b/tests/unit/test_typescript_sdk_release_workflow.py @@ -331,6 +331,8 @@ def _run_helper( tmp_path: Path, state: str, *, + publish_release: str | None = "true", + remote_tamper: str | None = None, ambiguous: bool = False, reconcile_only: bool = False, tag_kind: str = "tag", @@ -354,6 +356,8 @@ def _run_helper( (bundle / MANIFEST).write_bytes(transported_manifest) (remote / ARTIFACT).write_bytes(artifact_bytes) (remote / MANIFEST).write_bytes(manifest_bytes) + if remote_tamper: + (remote / remote_tamper).write_bytes(b"tampered") gh = fake_bin / "gh" gh.write_text(_fake_gh_source(), encoding="utf-8") gh.chmod(0o755) @@ -395,6 +399,9 @@ def _run_helper( "WORKFLOW_SHA256": "c" * 64, } ) + env.pop("PUBLISH_RELEASE", None) + if publish_release is not None: + env["PUBLISH_RELEASE"] = publish_release result = subprocess.run( ["bash", str(PUBLISH_HELPER)], cwd=tmp_path, @@ -441,7 +448,9 @@ def test_publish_executes_hostile_preconditions_before_each_effect( assert [call[1] for call in _effects(calls)] == ["create", "upload", "upload", "edit"] assert len([call for call in calls[:edit_index] if call[:2] == ["release", "download"]]) == 2 assert len([call for call in calls if call[:2] == ["release", "verify-asset"]]) == 2 - assert (tmp_path / "github-output").read_text() == f"url={RELEASE_URL}sdk-v0.2.1\n" + assert ( + tmp_path / "github-output" + ).read_text() == f"url={RELEASE_URL}sdk-v0.2.1\nrelease_state=published\n" @pytest.mark.parametrize("state", ["absent", "published"]) @@ -705,3 +714,95 @@ def test_release_files_do_not_reintroduce_service_quality_or_public_api_changes( ): assert forbidden not in _workflow().lower() assert not (ROOT / "scripts/verify_retrieval_release_qualification.py").exists() + + +@pytest.mark.parametrize("publish_release", [None, "false"]) +def test_draft_default_retains_only_mutable_byte_evidence(tmp_path, publish_release): + result, calls, state = _run_helper(tmp_path, "absent", publish_release=publish_release) + assert result.returncode == 0, result.stderr + assert json.loads(state.read_text()) == "draft" + assert [call[1] for call in _effects(calls)] == ["create", "upload", "upload"] + assert not any( + call[:2] in (["release", "verify"], ["release", "verify-asset"]) for call in calls + ) + receipt = json.loads( + ( + tmp_path / "draft-qualification/infinity-context-sdk-draft-qualification-receipt.json" + ).read_text() + ) + assert receipt["release_state"] == "draft" + assert receipt["immutable_attestation_verified"] is False + assert receipt["source_commit"] == COMMIT + assert receipt["tag_object"] == TAG_OBJECT + assert receipt["release_id"] == 41 + assert receipt["observed_draft_url"] == DRAFT_URL + for asset in receipt["assets"]: + data = (tmp_path / "release-bundle" / asset["name"]).read_bytes() + assert asset["sha256"] == hashlib.sha256(data).hexdigest() + assert asset["byte_length"] == len(data) + assert not (tmp_path / "verification-receipt/release-attestation.json").exists() + assert (tmp_path / "github-output").read_text() == "release_state=draft\n" + + +@pytest.mark.parametrize("value", ["", "TRUE", "False", "1", "yes", " true", "false\n"]) +def test_invalid_publish_input_fails_before_any_api_call(tmp_path, value): + result, calls, _ = _run_helper(tmp_path, "absent", publish_release=value) + assert result.returncode != 0 + assert "PUBLISH_RELEASE must be true or false" in result.stderr + assert calls == [] + + +@pytest.mark.parametrize("asset", [ARTIFACT, MANIFEST]) +def test_draft_byte_drift_never_emits_receipt_or_publishes(tmp_path, asset): + result, calls, _ = _run_helper(tmp_path, "absent", publish_release="false", remote_tamper=asset) + assert result.returncode != 0 + assert not any(call[:2] == ["release", "edit"] for call in calls) + assert not (tmp_path / "draft-qualification").exists() + + +@pytest.mark.parametrize("publish_release", [None, "false", "true"]) +@pytest.mark.parametrize("reconcile_only", [False, True]) +@pytest.mark.parametrize("state", ["published", "draft", "absent"]) +def test_release_mode_reconciliation_matrix(tmp_path, publish_release, reconcile_only, state): + result, calls, _ = _run_helper( + tmp_path, state, publish_release=publish_release, reconcile_only=reconcile_only + ) + expected_success = state == "published" or (state == "absent" and not reconcile_only) + assert (result.returncode == 0) == expected_success, result.stderr + if state != "absent" or reconcile_only: + assert _effects(calls) == [] + if state == "published": + assert any(call[:2] == ["release", "verify"] for call in calls) + assert not (tmp_path / "draft-qualification").exists() + + +def test_workflow_defaults_and_receipt_branches(): + document = _document() + inputs = document.get("on", document.get(True))["workflow_dispatch"]["inputs"] + assert inputs["publish_release"]["type"] == "boolean" + assert inputs["publish_release"]["default"] is False + steps = _steps("publish") + effect = next(step for step in steps if step.get("id") == "publish") + assert effect["env"]["PUBLISH_RELEASE"] == "${{ inputs.publish_release }}" + for step in steps: + if step.get("name") in ( + "Create non-release verification receipt", + "Retain verification receipt for Discord and operations custody", + ): + assert step["if"] == "steps.publish.outputs.release_state == 'published'" + draft = next( + step for step in steps if step.get("name") == "Retain mutable draft qualification evidence" + ) + assert draft["if"] == "steps.publish.outputs.release_state == 'draft'" + assert draft["with"]["retention-days"] == 90 + + +def test_published_receipt_verifier_rejects_draft(tmp_path): + args, output, _ = _receipt_fixture(tmp_path) + path = tmp_path / "evidence/release.json" + release = json.loads(path.read_text()) + release["draft"] = True + path.write_text(json.dumps(release)) + result = subprocess.run(args, check=False, capture_output=True, text=True) + assert result.returncode != 0 + assert not output.exists() From df5e233d5c93fa3749f18460e4fd82c7e637996a Mon Sep 17 00:00:00 2001 From: iliya Date: Thu, 10 Sep 2026 00:38:01 +0000 Subject: [PATCH 2/3] fix(release): ignore volatile draft download metadata --- .../scripts/sdk-release-publish.sh | 10 ++- .../test_typescript_sdk_release_workflow.py | 65 ++++++++++++++++++- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh b/packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh index 88eefc0c..f018ecda 100755 --- a/packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh +++ b/packages/infinity_context_ts_sdk/scripts/sdk-release-publish.sh @@ -232,14 +232,20 @@ elif [ "${PUBLISH_RELEASE}" = true ]; then 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="$(jq -Sc '{id, tag_name, name, draft, prerelease, html_url, assets}' <<<"${RELEASE_JSON}")" + 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 "$(jq -Sc '{id, tag_name, name, draft, prerelease, html_url, assets}' <<<"${RELEASE_JSON}")" = "${observed_release}" || \ + test "$(draft_identity <<<"${RELEASE_JSON}")" = "${observed_release}" || \ die "Draft identity or assets changed during qualification" mkdir draft-qualification asset_evidence='[]' diff --git a/tests/unit/test_typescript_sdk_release_workflow.py b/tests/unit/test_typescript_sdk_release_workflow.py index 21faaf4c..01d384a0 100644 --- a/tests/unit/test_typescript_sdk_release_workflow.py +++ b/tests/unit/test_typescript_sdk_release_workflow.py @@ -216,11 +216,13 @@ def _release(state: str, artifact_bytes: bytes, manifest_bytes: bytes) -> dict[s "assets": [ { "id": 51, + "size": len(artifact_bytes), "name": ARTIFACT, "digest": f"sha256:{hashlib.sha256(artifact_bytes).hexdigest()}", }, { "id": 52, + "size": len(manifest_bytes), "name": MANIFEST, "digest": f"sha256:{hashlib.sha256(manifest_bytes).hexdigest()}", }, @@ -250,6 +252,18 @@ def release_for(name): if name == "draft": value["html_url"] = os.environ["FAKE_DRAFT_URL"] value.update(json.loads(os.environ["FAKE_RELEASE_OVERRIDES"]).get(name, {})) + calls = [json.loads(line) for line in log_path.read_text().splitlines()] + downloads = [call for call in calls if call[:2] == ["release", "download"]] + for asset in value["assets"]: + asset["download_count"] = sum( + call[call.index("--pattern") + 1] == asset["name"] for call in downloads) + if downloads: + drift = json.loads(os.environ["FAKE_AFTER_DOWNLOAD"]) + for asset in value["assets"]: + asset.update(drift.get("assets", {}).get(asset["name"], {})) + value.update(drift.get("release", {})) + if drift.get("reverse_assets"): + value["assets"].reverse() return value if args[0] == "api": @@ -339,6 +353,7 @@ def _run_helper( verify_fail: bool = False, draft_url: str = DRAFT_URL, release_overrides: dict[str, dict[str, object]] | None = None, + after_download: dict[str, object] | None = None, ) -> tuple[subprocess.CompletedProcess[str], list[list[str]], Path]: artifact_bytes = b"exact pack-once bytes\n" manifest_bytes = b'{"build_workflow_run_attempt":2,"build_workflow_run_id":12345}\n' @@ -375,6 +390,7 @@ def _run_helper( { "FAKE_ATTESTATION_JSON": json.dumps(_attestation(artifact_bytes, manifest_bytes)), "FAKE_DRAFT_URL": draft_url, + "FAKE_AFTER_DOWNLOAD": json.dumps(after_download or {}), "FAKE_RELEASE_OVERRIDES": json.dumps(release_overrides or {}), "FAKE_EDIT_AMBIGUOUS": str(ambiguous).lower(), "FAKE_GH_LOG": str(log_path), @@ -717,8 +733,16 @@ def test_release_files_do_not_reintroduce_service_quality_or_public_api_changes( @pytest.mark.parametrize("publish_release", [None, "false"]) -def test_draft_default_retains_only_mutable_byte_evidence(tmp_path, publish_release): - result, calls, state = _run_helper(tmp_path, "absent", publish_release=publish_release) +@pytest.mark.parametrize("reverse_assets", [False, True]) +def test_draft_counter_increase_retains_only_mutable_byte_evidence( + tmp_path, publish_release, reverse_assets +): + result, calls, state = _run_helper( + tmp_path, + "absent", + publish_release=publish_release, + after_download={"reverse_assets": reverse_assets}, + ) assert result.returncode == 0, result.stderr assert json.loads(state.read_text()) == "draft" assert [call[1] for call in _effects(calls)] == ["create", "upload", "upload"] @@ -806,3 +830,40 @@ def test_published_receipt_verifier_rejects_draft(tmp_path): result = subprocess.run(args, check=False, capture_output=True, text=True) assert result.returncode != 0 assert not output.exists() + + +@pytest.mark.parametrize("asset", [ARTIFACT, MANIFEST]) +@pytest.mark.parametrize( + "change", + [ + {"id": 99}, + {"name": "other.tgz"}, + {"size": 999}, + {"digest": "sha256:" + "f" * 64}, + {"digest": None}, + ], +) +def test_draft_asset_drift_after_download_fails(tmp_path, asset, change): + result, calls, _ = _run_helper( + tmp_path, + "absent", + publish_release="false", + after_download={"assets": {asset: change}}, + ) + assert result.returncode != 0 + assert "identity or asset" in result.stderr + assert [call[1] for call in _effects(calls)] == ["create", "upload", "upload"] + assert not (tmp_path / "draft-qualification").exists() + + +def test_draft_release_id_drift_after_download_fails(tmp_path): + result, calls, _ = _run_helper( + tmp_path, + "absent", + publish_release="false", + after_download={"release": {"id": 99}}, + ) + assert result.returncode != 0 + assert "Draft identity or assets changed" in result.stderr + assert [call[1] for call in _effects(calls)] == ["create", "upload", "upload"] + assert not (tmp_path / "draft-qualification").exists() From df11ec4b3a4a0ee77c90358f7acf9935e7c6bd8b Mon Sep 17 00:00:00 2001 From: iliya Date: Thu, 10 Sep 2026 00:44:03 +0000 Subject: [PATCH 3/3] chore(sdk): prepare version 0.3.0 for v3 qualification --- packages/infinity_context_ts_sdk/package-lock.json | 4 ++-- packages/infinity_context_ts_sdk/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/infinity_context_ts_sdk/package-lock.json b/packages/infinity_context_ts_sdk/package-lock.json index 96c63681..1b6a64f3 100644 --- a/packages/infinity_context_ts_sdk/package-lock.json +++ b/packages/infinity_context_ts_sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@infinity-context/sdk", - "version": "0.2.4", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@infinity-context/sdk", - "version": "0.2.4", + "version": "0.3.0", "license": "Apache-2.0", "bin": { "infinity-context-full-memory-proof": "scripts/full-memory-proof.mjs", diff --git a/packages/infinity_context_ts_sdk/package.json b/packages/infinity_context_ts_sdk/package.json index b1d14e5a..f14f1798 100644 --- a/packages/infinity_context_ts_sdk/package.json +++ b/packages/infinity_context_ts_sdk/package.json @@ -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": {