Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
27 changes: 27 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
# SPDX-License-Identifier: MIT

set -euo pipefail

staged_files="$(git diff --cached --name-only --diff-filter=ACMR)"
go_dependency_change='^[+-][[:space:]]*(require|replace|exclude)[[:space:]]|^[+-][[:space:]]+[^[:space:]]+[[:space:]]+v[0-9]'
if git diff --cached --unified=0 -- go.mod | grep -Eq "$go_dependency_change"; then
dependency_change=1
else
package_dependency_change="$(node -e '
const dependencies = value => ({
dependencies: value.dependencies ?? {},
optionalDependencies: value.optionalDependencies ?? {},
});
const [before, after] = process.argv.slice(1).map(JSON.parse);
process.stdout.write(JSON.stringify(dependencies(before)) === JSON.stringify(dependencies(after)) ? "0" : "1");
' "$(git show HEAD:package.json 2>/dev/null || printf '{}')" "$(git show :package.json 2>/dev/null || printf '{}')")"
dependency_change="$package_dependency_change"
fi

if [ "$dependency_change" = 1 ] \
&& ! printf '%s\n' "$staged_files" | grep -Fxq 'THIRD_PARTY_NOTICES.md'; then
echo "Dependency metadata changed. Run 'make third-party-notices', review it, and stage THIRD_PARTY_NOTICES.md." >&2
exit 1
fi
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ jobs:
# ── Layer 4: Security & Compliance (parallel with L2-L3) ──────────
security:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
Expand All @@ -488,6 +489,10 @@ jobs:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: '3.x'
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: '22.14.0'
package-manager-cache: false
- name: Fetch meta data
run: python3 scripts/fetch_meta.py
- name: Gitleaks
Expand All @@ -501,6 +506,10 @@ jobs:
run: go run golang.org/x/vuln/cmd/govulncheck@v1.1.4 ./...
- name: Check dependency licenses
run: go run github.com/google/go-licenses/v2@v2.0.1 check ./... --disallowed_types=forbidden,restricted,reciprocal,unknown
- name: Install pinned npm
run: npm install --global npm@11.16.0
- name: Check third-party notices
run: make check-third-party-notices

license-header:
if: ${{ github.event_name == 'pull_request' }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pkg-pr-new.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
publish:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
Expand Down
54 changes: 51 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,35 @@ jobs:
fs.appendFileSync(output, `source_sha=${sourceSha}\nversion=${result.data.tagVersion}\nchannel=${channel}\nprerelease=${channel === "beta"}\n`);
NODE

build-sign-notarize:
license-compliance:
needs: preflight
runs-on: ubuntu-22.04
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
ref: ${{ needs.preflight.outputs.source_sha }}
persist-credentials: false
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: '1.23'
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.x'
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: '22.14.0'
package-manager-cache: false
- name: Install pinned npm
run: npm install --global npm@11.16.0
- name: Check third-party notices
run: make check-third-party-notices

build-sign-notarize:
needs: [preflight, license-compliance]
runs-on: ubuntu-22.04
timeout-minutes: 45
# This is the single approval boundary for the release. It grants access to
# Apple signing material before the candidate is built.
Expand All @@ -97,6 +123,7 @@ jobs:
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
ref: ${{ needs.preflight.outputs.source_sha }}
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
Expand All @@ -111,6 +138,15 @@ jobs:
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false

- name: Verify tag still points to source commit
env:
SOURCE_SHA: ${{ needs.preflight.outputs.source_sha }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
git fetch --no-tags origin "refs/tags/${TAG}:refs/tags/${TAG}"
[[ "$(git rev-parse "refs/tags/${TAG}^{commit}")" == "$SOURCE_SHA" ]] || { echo "Release tag changed after preflight." >&2; exit 1; }

- name: Fetch build metadata
run: python3 scripts/fetch_meta.py

Expand Down Expand Up @@ -168,6 +204,12 @@ jobs:
run: |
set -euo pipefail
(cd dist && sha256sum --check checksums.txt)
for archive in dist/*.tar.gz; do
tar -tzf "$archive" | grep -Eq '(^|/)THIRD_PARTY_NOTICES\.md$' || { echo "${archive} is missing THIRD_PARTY_NOTICES.md." >&2; exit 1; }
done
for archive in dist/*.zip; do
unzip -Z1 "$archive" | grep -Eq '(^|/)THIRD_PARTY_NOTICES\.md$' || { echo "${archive} is missing THIRD_PARTY_NOTICES.md." >&2; exit 1; }
done
mkdir release-candidate
cp dist/*.tar.gz dist/*.zip dist/checksums.txt release-candidate/
cp dist/CHANGELOG.md release-candidate/CHANGELOG.md
Expand All @@ -181,6 +223,9 @@ jobs:
throw new Error("npm pack did not produce the expected release package");
}
NODE
npm_archive="$(find release-candidate -maxdepth 1 -type f -name '*.tgz' -print -quit)"
[[ -n "$npm_archive" ]] || { echo "npm pack did not create a tarball." >&2; exit 1; }
tar -tzf "$npm_archive" | grep -Fxq 'package/THIRD_PARTY_NOTICES.md' || { echo "npm package is missing THIRD_PARTY_NOTICES.md." >&2; exit 1; }

- name: Upload release candidate
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
Expand Down Expand Up @@ -523,15 +568,16 @@ jobs:
esac

retry-guidance:
needs: [preflight, build-sign-notarize, create-draft-release, verify-macos, publish-github, publish-npm]
if: ${{ always() && (needs.preflight.result == 'failure' || needs.build-sign-notarize.result == 'failure' || needs.create-draft-release.result == 'failure' || needs.verify-macos.result == 'failure' || needs.publish-github.result == 'failure' || needs.publish-npm.result == 'failure') }}
needs: [preflight, license-compliance, build-sign-notarize, create-draft-release, verify-macos, publish-github, publish-npm]
if: ${{ always() && (needs.preflight.result == 'failure' || needs.license-compliance.result == 'failure' || needs.build-sign-notarize.result == 'failure' || needs.create-draft-release.result == 'failure' || needs.verify-macos.result == 'failure' || needs.publish-github.result == 'failure' || needs.publish-npm.result == 'failure') }}
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- name: Write retry guidance
env:
PREFLIGHT_RESULT: ${{ needs.preflight.result }}
LICENSE_RESULT: ${{ needs.license-compliance.result }}
BUILD_RESULT: ${{ needs.build-sign-notarize.result }}
DRAFT_RESULT: ${{ needs.create-draft-release.result }}
VERIFY_RESULT: ${{ needs.verify-macos.result }}
Expand All @@ -547,6 +593,7 @@ jobs:
echo "| Job | Result |"
echo "| --- | --- |"
echo "| preflight | ${PREFLIGHT_RESULT} |"
echo "| license-compliance | ${LICENSE_RESULT} |"
echo "| build-sign-notarize | ${BUILD_RESULT} |"
echo "| create-draft-release | ${DRAFT_RESULT} |"
echo "| verify-macos | ${VERIFY_RESULT} |"
Expand All @@ -557,6 +604,7 @@ jobs:
'Select the recovery action from the failed-step diagnosis. If a Draft or public Release already exists, use **Re-run failed jobs**, not **Re-run all jobs**:' \
'' \
'- **preflight:** network or fetch failure → retry preflight. Version/tag validation failure → correct it, then create a new tag.' \
'- **license-compliance:** dependency registry failure → retry this job. Missing, unidentifiable, or currently unsupported license → update the dependency/notices or complete manual review, then create a new tag.' \
'- **build-sign-notarize:** transient build/service failure → retry build. Code or release configuration issue → correct it, then create a new tag.' \
'- **create-draft-release:** GitHub Draft Release API/upload failure → retry draft; a partial Draft is verified and only missing assets are uploaded. Release-candidate inconsistency → delete the Draft, then retry build.' \
'- **verify-macos:** runner or network failure → retry only the failed matrix child. Checksum, signing, notarization, or runtime failure → delete the Draft, then retry build.' \
Expand Down
1 change: 1 addition & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ archives:
- README.md
- LICENSE
- CHANGELOG.md
- THIRD_PARTY_NOTICES.md

checksum:
name_template: checksums.txt
Expand Down
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ PREFIX ?= /usr/local
TEST_GOARCH := $(or $(GOARCH),$(shell go env GOARCH))
RACE_FLAG := $(if $(filter riscv64,$(TEST_GOARCH)),,-race)

.PHONY: all build vet fmt-check script-test test unit-test live-skills-test integration-test examples-build quality-gate install uninstall clean fetch_meta gitleaks sidecar-test
.PHONY: all build vet fmt-check script-test test unit-test live-skills-test integration-test examples-build quality-gate install uninstall clean fetch_meta gitleaks sidecar-test third-party-notices check-third-party-notices install-git-hooks

all: test

Expand All @@ -48,12 +48,24 @@ fmt-check:
fi

script-test:
python3 scripts/third_party_notices.test.py
bash scripts/resolve-changed-from.test.sh
bash scripts/ci-workflow.test.sh
bash scripts/release-workflow.test.sh
bash scripts/semantic-review-workflow.test.sh
bash scripts/third_party_notices_hook.test.sh
$(NODE) --test scripts/e2e_domains.test.js scripts/fetch_e2e_tat.test.js scripts/install.test.js scripts/release-preflight.test.js scripts/release-publish-policy.test.js scripts/semantic-review-verify-artifact.test.js scripts/pr-quality-summary.test.js scripts/semantic-review-publish.test.js scripts/ci-quality-summary-publish.test.js

third-party-notices:
python3 scripts/third_party_notices.py generate --output THIRD_PARTY_NOTICES.md

check-third-party-notices:
python3 scripts/third_party_notices.py check --output THIRD_PARTY_NOTICES.md

install-git-hooks:
git config core.hooksPath .githooks
@echo "Installed repository Git hooks."

# ./extension/... keeps the public plugin SDK in the default test matrix.
unit-test: fetch_meta
go test $(RACE_FLAG) -gcflags="all=-N -l" -count=1 \
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ For major changes, we recommend discussing with us first via an Issue.

Before opening a PR, see [AGENTS.md](./AGENTS.md) for the local build, test, and PR checklist used by contributors and AI agents.

### Third-party dependency notices

Install the repository hook once with `make install-git-hooks`. When adding, removing, or upgrading a Go or npm production dependency, run `make third-party-notices`, review the generated `THIRD_PARTY_NOTICES.md`, and commit it with the dependency change. The local hook blocks a staged dependency declaration without the notice file; CI independently verifies that the generated file is current. The automated verifier currently covers MIT, BSD-2-Clause, BSD-3-Clause, ISC, and Apache-2.0; another or unidentifiable license is blocked for manual review, rather than treated as a company-wide prohibition.

Comment thread
Tantanz20020918 marked this conversation as resolved.
## License

This project is licensed under the **MIT License**.
Expand Down
Loading
Loading