@indigoai-us/hq-cloud 6.15.0 → 6.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/sync-mutation.d.ts +16 -0
- package/dist/bin/sync-mutation.d.ts.map +1 -0
- package/dist/bin/sync-mutation.js +60 -0
- package/dist/bin/sync-mutation.js.map +1 -0
- package/dist/bin/sync-mutation.test.d.ts +2 -0
- package/dist/bin/sync-mutation.test.d.ts.map +1 -0
- package/dist/bin/sync-mutation.test.js +165 -0
- package/dist/bin/sync-mutation.test.js.map +1 -0
- package/dist/bin/sync-runner-company.d.ts +8 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +16 -0
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-company.test.d.ts +2 -0
- package/dist/bin/sync-runner-company.test.d.ts.map +1 -0
- package/dist/bin/sync-runner-company.test.js +36 -0
- package/dist/bin/sync-runner-company.test.js.map +1 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +98 -8
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +17 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +109 -0
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/conflict-recovery.test.d.ts +2 -0
- package/dist/cli/conflict-recovery.test.d.ts.map +1 -0
- package/dist/cli/conflict-recovery.test.js +201 -0
- package/dist/cli/conflict-recovery.test.js.map +1 -0
- package/dist/cli/conflict.d.ts +60 -0
- package/dist/cli/conflict.d.ts.map +1 -1
- package/dist/cli/conflict.js +333 -0
- package/dist/cli/conflict.js.map +1 -1
- package/dist/cli/sync.d.ts +35 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +100 -0
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +85 -1
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/skill-telemetry.d.ts +6 -0
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +14 -2
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +79 -0
- package/dist/skill-telemetry.test.js.map +1 -1
- package/dist/sync/candidate-uploader.d.ts +88 -0
- package/dist/sync/candidate-uploader.d.ts.map +1 -0
- package/dist/sync/candidate-uploader.js +212 -0
- package/dist/sync/candidate-uploader.js.map +1 -0
- package/dist/sync/candidate-uploader.test.d.ts +2 -0
- package/dist/sync/candidate-uploader.test.d.ts.map +1 -0
- package/dist/sync/candidate-uploader.test.js +132 -0
- package/dist/sync/candidate-uploader.test.js.map +1 -0
- package/dist/sync/delta-client.d.ts +73 -0
- package/dist/sync/delta-client.d.ts.map +1 -0
- package/dist/sync/delta-client.js +201 -0
- package/dist/sync/delta-client.js.map +1 -0
- package/dist/sync/delta-client.test.d.ts +2 -0
- package/dist/sync/delta-client.test.d.ts.map +1 -0
- package/dist/sync/delta-client.test.js +97 -0
- package/dist/sync/delta-client.test.js.map +1 -0
- package/dist/sync/durable-apply.d.ts +76 -0
- package/dist/sync/durable-apply.d.ts.map +1 -0
- package/dist/sync/durable-apply.js +530 -0
- package/dist/sync/durable-apply.js.map +1 -0
- package/dist/sync/durable-apply.test.d.ts +2 -0
- package/dist/sync/durable-apply.test.d.ts.map +1 -0
- package/dist/sync/durable-apply.test.js +180 -0
- package/dist/sync/durable-apply.test.js.map +1 -0
- package/dist/sync/event-sync.d.ts +33 -1
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +149 -1
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/event-sync.test.js +142 -1
- package/dist/sync/event-sync.test.js.map +1 -1
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/multipart-uploader.d.ts +99 -0
- package/dist/sync/multipart-uploader.d.ts.map +1 -0
- package/dist/sync/multipart-uploader.js +447 -0
- package/dist/sync/multipart-uploader.js.map +1 -0
- package/dist/sync/multipart-uploader.test.d.ts +2 -0
- package/dist/sync/multipart-uploader.test.d.ts.map +1 -0
- package/dist/sync/multipart-uploader.test.js +119 -0
- package/dist/sync/multipart-uploader.test.js.map +1 -0
- package/dist/sync/mutation-client.d.ts +85 -0
- package/dist/sync/mutation-client.d.ts.map +1 -0
- package/dist/sync/mutation-client.js +245 -0
- package/dist/sync/mutation-client.js.map +1 -0
- package/dist/sync/mutation-client.test.d.ts +2 -0
- package/dist/sync/mutation-client.test.d.ts.map +1 -0
- package/dist/sync/mutation-client.test.js +51 -0
- package/dist/sync/mutation-client.test.js.map +1 -0
- package/dist/sync/push-receiver.d.ts +45 -0
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +101 -0
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +54 -2
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/sync/scope-inventory-client.d.ts +69 -0
- package/dist/sync/scope-inventory-client.d.ts.map +1 -0
- package/dist/sync/scope-inventory-client.js +210 -0
- package/dist/sync/scope-inventory-client.js.map +1 -0
- package/dist/sync/scope-inventory-client.test.d.ts +2 -0
- package/dist/sync/scope-inventory-client.test.d.ts.map +1 -0
- package/dist/sync/scope-inventory-client.test.js +94 -0
- package/dist/sync/scope-inventory-client.test.js.map +1 -0
- package/dist/sync/snapshot-client.d.ts +98 -0
- package/dist/sync/snapshot-client.d.ts.map +1 -0
- package/dist/sync/snapshot-client.js +402 -0
- package/dist/sync/snapshot-client.js.map +1 -0
- package/dist/sync/snapshot-client.test.d.ts +2 -0
- package/dist/sync/snapshot-client.test.d.ts.map +1 -0
- package/dist/sync/snapshot-client.test.js +169 -0
- package/dist/sync/snapshot-client.test.js.map +1 -0
- package/dist/sync/uploader-finalization.d.ts +97 -0
- package/dist/sync/uploader-finalization.d.ts.map +1 -0
- package/dist/sync/uploader-finalization.js +273 -0
- package/dist/sync/uploader-finalization.js.map +1 -0
- package/dist/sync/uploader-finalization.test.d.ts +2 -0
- package/dist/sync/uploader-finalization.test.d.ts.map +1 -0
- package/dist/sync/uploader-finalization.test.js +92 -0
- package/dist/sync/uploader-finalization.test.js.map +1 -0
- package/dist/telemetry.d.ts +11 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +21 -2
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.js +80 -0
- package/dist/telemetry.test.js.map +1 -1
- package/package.json +6 -1
- package/.claude/policies/hq-cloud-esm-cannot-spy-fs-builtins.md +0 -30
- package/.claude/policies/hq-cloud-strip-types-no-parameter-properties.md +0 -22
- package/.github/workflows/ci.yml +0 -84
- package/.github/workflows/publish.yml +0 -56
- package/.github/workflows/unreleased-commits-nag.yml +0 -256
- package/eslint.config.js +0 -67
- package/pnpm-workspace.yaml +0 -2
- package/scripts/presign-transport-e2e.mjs +0 -250
- package/scripts/vault-rebaseline.sh +0 -323
- package/scripts/vault-rescue.sh +0 -332
- package/src/active-company.test.ts +0 -188
- package/src/active-company.ts +0 -168
- package/src/agent-codex-instructions.test.ts +0 -332
- package/src/agent-codex-instructions.ts +0 -309
- package/src/auth.ts +0 -146
- package/src/backup-prune.test.ts +0 -98
- package/src/backup-prune.ts +0 -182
- package/src/bin/backup-prune-runner.ts +0 -33
- package/src/bin/rescue-runner.ts +0 -25
- package/src/bin/sync-runner-company.ts +0 -695
- package/src/bin/sync-runner-events.test.ts +0 -143
- package/src/bin/sync-runner-events.ts +0 -55
- package/src/bin/sync-runner-planning.test.ts +0 -311
- package/src/bin/sync-runner-planning.ts +0 -258
- package/src/bin/sync-runner-rollup.test.ts +0 -37
- package/src/bin/sync-runner-rollup.ts +0 -97
- package/src/bin/sync-runner-telemetry.ts +0 -15
- package/src/bin/sync-runner-watch-loop.ts +0 -1235
- package/src/bin/sync-runner-watch-routes.test.ts +0 -71
- package/src/bin/sync-runner-watch-routes.ts +0 -184
- package/src/bin/sync-runner.test.ts +0 -8767
- package/src/bin/sync-runner.ts +0 -2190
- package/src/cli/accept.ts +0 -124
- package/src/cli/conflict.ts +0 -119
- package/src/cli/doctor.test.ts +0 -581
- package/src/cli/doctor.ts +0 -642
- package/src/cli/index.ts +0 -49
- package/src/cli/invite.test.ts +0 -250
- package/src/cli/invite.ts +0 -214
- package/src/cli/promote.ts +0 -157
- package/src/cli/reindex-knowledge.test.ts +0 -307
- package/src/cli/reindex-knowledge.ts +0 -450
- package/src/cli/reindex.test.ts +0 -957
- package/src/cli/reindex.ts +0 -979
- package/src/cli/rescue-classify-ordering.test.ts +0 -548
- package/src/cli/rescue-clone-diagnostics.test.ts +0 -120
- package/src/cli/rescue-core.ts +0 -3011
- package/src/cli/rescue-drift-reconcile.test.ts +0 -179
- package/src/cli/rescue-drop-dir-symlink.test.ts +0 -224
- package/src/cli/rescue-exec-bit-preserve.test.ts +0 -187
- package/src/cli/rescue-hq-root-guard.test.ts +0 -232
- package/src/cli/rescue-journal-reconcile.test.ts +0 -215
- package/src/cli/rescue-mtime-preserve.test.ts +0 -203
- package/src/cli/rescue-settings-reconcile.test.ts +0 -637
- package/src/cli/rescue-snapshot.test.ts +0 -57
- package/src/cli/rescue-snapshot.ts +0 -51
- package/src/cli/rescue.reindex.test.ts +0 -63
- package/src/cli/rescue.test.ts +0 -131
- package/src/cli/rescue.ts +0 -182
- package/src/cli/share.test.ts +0 -7843
- package/src/cli/share.ts +0 -3663
- package/src/cli/sync-scope.test.ts +0 -652
- package/src/cli/sync.test.ts +0 -5207
- package/src/cli/sync.ts +0 -3470
- package/src/cli/tombstones.ts +0 -106
- package/src/cli/watch-event-push-conflict.test.ts +0 -234
- package/src/client-info.test.ts +0 -214
- package/src/client-info.ts +0 -121
- package/src/cognito-auth.test.ts +0 -712
- package/src/cognito-auth.ts +0 -1422
- package/src/company-resolver.test.ts +0 -618
- package/src/company-resolver.ts +0 -521
- package/src/context.test.ts +0 -583
- package/src/context.ts +0 -378
- package/src/daemon-worker.ts +0 -26
- package/src/daemon.ts +0 -99
- package/src/entity-resolver.test.ts +0 -315
- package/src/entity-resolver.ts +0 -180
- package/src/ignore.test.ts +0 -466
- package/src/ignore.ts +0 -469
- package/src/index.ts +0 -439
- package/src/journal.test.ts +0 -968
- package/src/journal.ts +0 -765
- package/src/lib/cloud-authoritative.test.ts +0 -45
- package/src/lib/cloud-authoritative.ts +0 -59
- package/src/lib/conflict-file.ts +0 -86
- package/src/lib/conflict-index.ts +0 -289
- package/src/lib/conflict.test.ts +0 -348
- package/src/lib/describe-error.test.ts +0 -100
- package/src/lib/describe-error.ts +0 -58
- package/src/lib/exit-codes.ts +0 -24
- package/src/lib/machine-id.test.ts +0 -231
- package/src/lib/machine-id.ts +0 -175
- package/src/lib/net-errors.test.ts +0 -65
- package/src/lib/net-errors.ts +0 -86
- package/src/lib/readlink-safe.test.ts +0 -43
- package/src/lib/readlink-safe.ts +0 -29
- package/src/local-path-codec.test.ts +0 -138
- package/src/local-path-codec.ts +0 -161
- package/src/machine-auth.test.ts +0 -1323
- package/src/manifest-reconcile.test.ts +0 -1123
- package/src/manifest-reconcile.ts +0 -518
- package/src/object-io.test.ts +0 -1221
- package/src/object-io.ts +0 -1306
- package/src/operation-lock.test.ts +0 -484
- package/src/operation-lock.ts +0 -680
- package/src/outcome-telemetry.test.ts +0 -498
- package/src/outcome-telemetry.ts +0 -639
- package/src/personal-vault-exclusions.test.ts +0 -308
- package/src/personal-vault-exclusions.ts +0 -354
- package/src/personal-vault.test.ts +0 -756
- package/src/personal-vault.ts +0 -496
- package/src/prefix-coalesce.test.ts +0 -240
- package/src/prefix-coalesce.ts +0 -273
- package/src/public-surface.test.ts +0 -117
- package/src/qmd-reindex.test.ts +0 -877
- package/src/qmd-reindex.ts +0 -842
- package/src/read-only-state-dir.test.ts +0 -188
- package/src/remote-pull.test.ts +0 -1130
- package/src/remote-pull.ts +0 -618
- package/src/s3.symlink-materialize.test.ts +0 -492
- package/src/s3.test.ts +0 -1789
- package/src/s3.ts +0 -1532
- package/src/schemas/signal-types.test.ts +0 -82
- package/src/schemas/signal-types.ts +0 -38
- package/src/schemas/source-channels.test.ts +0 -82
- package/src/schemas/source-channels.ts +0 -53
- package/src/scope-shrink.test.ts +0 -633
- package/src/scope-shrink.ts +0 -481
- package/src/signals/get.test.ts +0 -310
- package/src/signals/get.ts +0 -75
- package/src/signals/internals.ts +0 -195
- package/src/signals/list.test.ts +0 -420
- package/src/signals/list.ts +0 -79
- package/src/signals/parse.ts +0 -8
- package/src/signals/types.ts +0 -91
- package/src/skill-telemetry.test.ts +0 -1825
- package/src/skill-telemetry.ts +0 -1439
- package/src/sources/get.test.ts +0 -293
- package/src/sources/get.ts +0 -66
- package/src/sources/internals.ts +0 -198
- package/src/sources/list.test.ts +0 -402
- package/src/sources/list.ts +0 -84
- package/src/sources/parse.ts +0 -43
- package/src/sources/types.ts +0 -84
- package/src/sync/event-sync.test.ts +0 -594
- package/src/sync/event-sync.ts +0 -545
- package/src/sync/feature-flags.test.ts +0 -378
- package/src/sync/feature-flags.ts +0 -62
- package/src/sync/index.ts +0 -76
- package/src/sync/lease-client.test.ts +0 -128
- package/src/sync/lease-client.ts +0 -207
- package/src/sync/logger.test.ts +0 -242
- package/src/sync/logger.ts +0 -79
- package/src/sync/metrics.test.ts +0 -462
- package/src/sync/metrics.ts +0 -213
- package/src/sync/pull-scope.ts +0 -265
- package/src/sync/push-event.test.ts +0 -266
- package/src/sync/push-event.ts +0 -224
- package/src/sync/push-receiver.test.ts +0 -566
- package/src/sync/push-receiver.ts +0 -1048
- package/src/sync/push-transport.ts +0 -231
- package/src/sync/realtime-rollout.test.ts +0 -86
- package/src/sync/realtime-rollout.ts +0 -262
- package/src/sync/state-store.test.ts +0 -194
- package/src/sync/state-store.ts +0 -727
- package/src/sync-core.ts +0 -58
- package/src/sync-progress.test.ts +0 -94
- package/src/sync-progress.ts +0 -140
- package/src/telemetry-events.test.ts +0 -88
- package/src/telemetry-events.ts +0 -205
- package/src/telemetry.test.ts +0 -1280
- package/src/telemetry.ts +0 -1109
- package/src/types.ts +0 -314
- package/src/vault-client.test.ts +0 -1380
- package/src/vault-client.ts +0 -1694
- package/src/version.ts +0 -24
- package/src/watch-roots.test.ts +0 -278
- package/src/watch-roots.ts +0 -162
- package/src/watcher-event-gate.test.ts +0 -212
- package/src/watcher.test.ts +0 -1079
- package/src/watcher.ts +0 -1741
- package/test/e2e/sync/cross-tenant-isolation.test.ts +0 -630
- package/test/e2e/sync/skill-telemetry-oversized-transcript.test.ts +0 -124
- package/test/e2e/sync/transient-company-leg.test.ts +0 -384
- package/test/e2e/sync/windows-unreadable-link-leg.test.ts +0 -191
- package/test/e2e/watcher-real-chokidar.test.ts +0 -165
- package/test/e2e/watcher-recursive-backend.test.ts +0 -181
- package/test/e2e/watcher-scoped-coverage.test.ts +0 -381
- package/test/invite-flow.integration.test.ts +0 -244
- package/test/joiner-manifest-reconcile.integration.test.ts +0 -322
- package/test/share-sync.integration.test.ts +0 -213
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -22
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
name: Nag unreleased commits on main
|
|
2
|
-
|
|
3
|
-
# Two weekly release nags for hq-cloud:
|
|
4
|
-
#
|
|
5
|
-
# nag — `main` is carrying work nobody has released (below).
|
|
6
|
-
# pin-lag — the latest hq-cloud release is not pinned by hq-cli and
|
|
7
|
-
# hq-desktop-app `main`. See that job's own header.
|
|
8
|
-
#
|
|
9
|
-
# A commit counts as UNRELEASED when it is reachable from `main` but not from
|
|
10
|
-
# any STABLE tag. Stable means `vMAJOR.MINOR.PATCH` with no prerelease suffix —
|
|
11
|
-
# `-rc.N`, `-beta.N` and `-alpha.N` tags deliberately do NOT silence the nag,
|
|
12
|
-
# because a prerelease is not a release.
|
|
13
|
-
#
|
|
14
|
-
# Release sync-back commits (`chore(release): …`) are ignored. The publish
|
|
15
|
-
# workflow pushes them to `main` *after* cutting the tag, so they are
|
|
16
|
-
# unreachable from that tag by construction and would nag forever otherwise.
|
|
17
|
-
#
|
|
18
|
-
# Releasing this repo is one command — `git push origin v<next>` — so the fix
|
|
19
|
-
# for a nag is always to cut the tag (or to say out loud why you are holding).
|
|
20
|
-
#
|
|
21
|
-
# Requires repo secret SLACK_BOT_TOKEN_RELEASE_MANAGER (the same release-manager
|
|
22
|
-
# bot hq-core's `release-pr-nag.yml` uses). The bot MUST be a member of #hq-dev.
|
|
23
|
-
# Without the secret the job logs a warning and exits 0 rather than failing.
|
|
24
|
-
|
|
25
|
-
on:
|
|
26
|
-
schedule:
|
|
27
|
-
# Mondays 15:00 UTC (~08:00 PT). Staggered against the sibling repos'
|
|
28
|
-
# nags so all four do not land in #hq-dev in the same second.
|
|
29
|
-
- cron: '10 15 * * 1'
|
|
30
|
-
workflow_dispatch:
|
|
31
|
-
inputs:
|
|
32
|
-
threshold_hours:
|
|
33
|
-
description: 'Age of the OLDEST unreleased commit, in hours, before nagging (default 24)'
|
|
34
|
-
required: false
|
|
35
|
-
default: '24'
|
|
36
|
-
|
|
37
|
-
permissions:
|
|
38
|
-
contents: read
|
|
39
|
-
|
|
40
|
-
jobs:
|
|
41
|
-
nag:
|
|
42
|
-
name: Nag when main has unreleased commits
|
|
43
|
-
if: github.repository == 'indigoai-us/hq-cloud'
|
|
44
|
-
runs-on: ubuntu-latest
|
|
45
|
-
env:
|
|
46
|
-
SLACK_TOKEN: ${{ secrets.SLACK_BOT_TOKEN_RELEASE_MANAGER }}
|
|
47
|
-
SLACK_CHANNEL: '#hq-dev'
|
|
48
|
-
THRESHOLD: ${{ github.event.inputs.threshold_hours || '24' }}
|
|
49
|
-
steps:
|
|
50
|
-
- name: Check out full history and tags
|
|
51
|
-
uses: actions/checkout@v4
|
|
52
|
-
with:
|
|
53
|
-
ref: main
|
|
54
|
-
fetch-depth: 0
|
|
55
|
-
|
|
56
|
-
- name: Find unreleased commits on main and nag
|
|
57
|
-
run: |
|
|
58
|
-
set -euo pipefail
|
|
59
|
-
|
|
60
|
-
if [ -z "${SLACK_TOKEN:-}" ]; then
|
|
61
|
-
echo "::warning::SLACK_BOT_TOKEN_RELEASE_MANAGER is not set on this repository; cannot post the nag."
|
|
62
|
-
exit 0
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
git fetch --force --tags --quiet origin main
|
|
66
|
-
|
|
67
|
-
# Newest stable tag reachable from main — display only.
|
|
68
|
-
LAST_TAG="$(git tag --list 'v*' --merged origin/main --sort=-v:refname \
|
|
69
|
-
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true)"
|
|
70
|
-
|
|
71
|
-
# Commits on main not reachable from ANY stable tag. `--exclude` applies
|
|
72
|
-
# to the `--tags` glob that follows it, so `v*-*` drops every prerelease.
|
|
73
|
-
git log origin/main --format='%H%x09%ct%x09%s' \
|
|
74
|
-
--not --exclude='v*-*' --tags='v*' > /tmp/unreleased_all.tsv
|
|
75
|
-
|
|
76
|
-
# Drop the post-tag release sync-back commits.
|
|
77
|
-
awk -F'\t' '$3 !~ /^chore\(release\)/' /tmp/unreleased_all.tsv > /tmp/unreleased.tsv
|
|
78
|
-
|
|
79
|
-
COUNT="$(wc -l < /tmp/unreleased.tsv | tr -d ' ')"
|
|
80
|
-
echo "Last stable tag: ${LAST_TAG:-<none>}"
|
|
81
|
-
echo "Unreleased commits on main: ${COUNT}"
|
|
82
|
-
|
|
83
|
-
if [ "$COUNT" -eq 0 ]; then
|
|
84
|
-
echo "main is fully released. Nothing to nag about."
|
|
85
|
-
exit 0
|
|
86
|
-
fi
|
|
87
|
-
|
|
88
|
-
OLDEST_TS="$(tail -1 /tmp/unreleased.tsv | cut -f2)"
|
|
89
|
-
NOW="$(date -u +%s)"
|
|
90
|
-
AGE_H=$(( (NOW - OLDEST_TS) / 3600 ))
|
|
91
|
-
echo "Oldest unreleased commit age: ${AGE_H}h (threshold ${THRESHOLD}h)"
|
|
92
|
-
|
|
93
|
-
if [ "$AGE_H" -lt "$THRESHOLD" ]; then
|
|
94
|
-
echo "Under threshold. Skipping."
|
|
95
|
-
exit 0
|
|
96
|
-
fi
|
|
97
|
-
|
|
98
|
-
REPO_NAME="${GITHUB_REPOSITORY##*/}"
|
|
99
|
-
DAYS=$(( AGE_H / 24 ))
|
|
100
|
-
if [ -n "$LAST_TAG" ]; then
|
|
101
|
-
LINK="https://github.com/${GITHUB_REPOSITORY}/compare/${LAST_TAG}...main"
|
|
102
|
-
TAG_LABEL="\`${LAST_TAG}\`"
|
|
103
|
-
else
|
|
104
|
-
LINK="https://github.com/${GITHUB_REPOSITORY}/commits/main"
|
|
105
|
-
TAG_LABEL="_never released_"
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
# Oldest-first sample of what is waiting, capped at 5 lines.
|
|
109
|
-
SAMPLE="$(tail -5 /tmp/unreleased.tsv | tac | cut -f3 | sed 's/^/• /')"
|
|
110
|
-
if [ "$COUNT" -gt 5 ]; then
|
|
111
|
-
SAMPLE="${SAMPLE}"$'\n'"• …and $(( COUNT - 5 )) more"
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
|
-
TEXT=":package: *\`${REPO_NAME}\`: ${COUNT} commit(s) have been sitting unreleased on \`main\` for ~${AGE_H}h (${DAYS}d).* Last stable release: ${TAG_LABEL} — prereleases don't count. Shipping is one command: \`git push origin v<next>\`. <${LINK}|See what's waiting>."$'\n'"${SAMPLE}"
|
|
115
|
-
|
|
116
|
-
RESP="$(curl -s -X POST \
|
|
117
|
-
-H "Authorization: Bearer ${SLACK_TOKEN}" \
|
|
118
|
-
-H 'Content-type: application/json; charset=utf-8' \
|
|
119
|
-
--data "$(jq -nc --arg c "$SLACK_CHANNEL" --arg t "$TEXT" '{channel:$c,text:$t,unfurl_links:false}')" \
|
|
120
|
-
https://slack.com/api/chat.postMessage)"
|
|
121
|
-
OK="$(echo "$RESP" | jq -r '.ok')"
|
|
122
|
-
echo "Slack post ok=${OK} err=$(echo "$RESP" | jq -r '.error // "-"')"
|
|
123
|
-
if [ "$OK" != "true" ]; then
|
|
124
|
-
echo "::error::Slack rejected the nag: $(echo "$RESP" | jq -r '.error // "unknown"')"
|
|
125
|
-
exit 1
|
|
126
|
-
fi
|
|
127
|
-
|
|
128
|
-
pin-lag:
|
|
129
|
-
name: Nag when consumers lag the latest hq-cloud release
|
|
130
|
-
# hq-cloud ships the sync runner that hq-cli and hq-desktop-app spawn. A
|
|
131
|
-
# release nobody has pinned is a release nobody is running, so this job
|
|
132
|
-
# compares the newest STABLE hq-cloud tag against the version each
|
|
133
|
-
# consumer's `main` actually declares:
|
|
134
|
-
#
|
|
135
|
-
# hq-cli pnpm-lock.yaml -> resolved @indigoai-us/hq-cloud version
|
|
136
|
-
# hq-desktop-app crates/hq-desktop-core/src/hq_cloud.rs -> HQ_CLOUD_VERSION
|
|
137
|
-
#
|
|
138
|
-
# Comparison is EXACT on the version string (the desktop pin's leading `~`
|
|
139
|
-
# is stripped first). A tilde range like `~6.14.47` does resolve to 6.14.48
|
|
140
|
-
# at npx spawn time, so this will flag a pin that is already functionally
|
|
141
|
-
# current — that is deliberate: the written pin is kept literally in step
|
|
142
|
-
# with the latest release.
|
|
143
|
-
#
|
|
144
|
-
# Reading the two sibling repos needs a cross-repo token, so this mints an
|
|
145
|
-
# installation token from the hq-audit-bot App (same pattern as
|
|
146
|
-
# hq-core's promote-to-hq-core.yml). The App must be installed on
|
|
147
|
-
# indigoai-us/hq-cli and indigoai-us/hq-desktop-app with `contents: read`.
|
|
148
|
-
if: github.repository == 'indigoai-us/hq-cloud'
|
|
149
|
-
runs-on: ubuntu-latest
|
|
150
|
-
env:
|
|
151
|
-
SLACK_TOKEN: ${{ secrets.SLACK_BOT_TOKEN_RELEASE_MANAGER }}
|
|
152
|
-
SLACK_CHANNEL: '#hq-dev'
|
|
153
|
-
HQ_AUDIT_BOT_APP_ID: ${{ secrets.HQ_AUDIT_BOT_APP_ID }}
|
|
154
|
-
HQ_AUDIT_BOT_PRIVATE_KEY: ${{ secrets.HQ_AUDIT_BOT_PRIVATE_KEY }}
|
|
155
|
-
steps:
|
|
156
|
-
- name: Check out full history and tags
|
|
157
|
-
uses: actions/checkout@v4
|
|
158
|
-
with:
|
|
159
|
-
ref: main
|
|
160
|
-
fetch-depth: 0
|
|
161
|
-
|
|
162
|
-
- name: Preflight credentials
|
|
163
|
-
id: pre
|
|
164
|
-
run: |
|
|
165
|
-
set -euo pipefail
|
|
166
|
-
if [ -z "${SLACK_TOKEN:-}" ]; then
|
|
167
|
-
echo "::warning::SLACK_BOT_TOKEN_RELEASE_MANAGER is not set on this repository; cannot post the nag."
|
|
168
|
-
echo "ready=false" >> "$GITHUB_OUTPUT"
|
|
169
|
-
exit 0
|
|
170
|
-
fi
|
|
171
|
-
if [ -z "${HQ_AUDIT_BOT_APP_ID:-}" ] || [ -z "${HQ_AUDIT_BOT_PRIVATE_KEY:-}" ]; then
|
|
172
|
-
echo "::warning::hq-audit-bot App credentials are not set; cannot read hq-cli / hq-desktop-app main."
|
|
173
|
-
echo "ready=false" >> "$GITHUB_OUTPUT"
|
|
174
|
-
exit 0
|
|
175
|
-
fi
|
|
176
|
-
echo "ready=true" >> "$GITHUB_OUTPUT"
|
|
177
|
-
|
|
178
|
-
- name: Mint installation token for the consumer repos
|
|
179
|
-
id: app-token
|
|
180
|
-
if: steps.pre.outputs.ready == 'true'
|
|
181
|
-
uses: actions/create-github-app-token@v1
|
|
182
|
-
with:
|
|
183
|
-
app-id: ${{ env.HQ_AUDIT_BOT_APP_ID }}
|
|
184
|
-
private-key: ${{ env.HQ_AUDIT_BOT_PRIVATE_KEY }}
|
|
185
|
-
owner: indigoai-us
|
|
186
|
-
repositories: |
|
|
187
|
-
hq-cli
|
|
188
|
-
hq-desktop-app
|
|
189
|
-
|
|
190
|
-
- name: Compare consumer pins against the latest hq-cloud release
|
|
191
|
-
if: steps.pre.outputs.ready == 'true'
|
|
192
|
-
env:
|
|
193
|
-
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
194
|
-
run: |
|
|
195
|
-
set -euo pipefail
|
|
196
|
-
|
|
197
|
-
git fetch --force --tags --quiet origin main
|
|
198
|
-
|
|
199
|
-
LATEST_TAG="$(git tag --list 'v*' --merged origin/main --sort=-v:refname \
|
|
200
|
-
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true)"
|
|
201
|
-
if [ -z "$LATEST_TAG" ]; then
|
|
202
|
-
echo "No stable hq-cloud release yet; nothing to compare against."
|
|
203
|
-
exit 0
|
|
204
|
-
fi
|
|
205
|
-
LATEST="${LATEST_TAG#v}"
|
|
206
|
-
echo "Latest stable hq-cloud release: ${LATEST}"
|
|
207
|
-
|
|
208
|
-
# --- hq-cli: resolved version in the lockfile's importers section ---
|
|
209
|
-
gh api "repos/indigoai-us/hq-cli/contents/pnpm-lock.yaml?ref=main" \
|
|
210
|
-
-H 'Accept: application/vnd.github.raw' > /tmp/hq-cli-lock.yaml
|
|
211
|
-
CLI_PIN="$(grep -A2 "^ '@indigoai-us/hq-cloud':" /tmp/hq-cli-lock.yaml \
|
|
212
|
-
| awk '/^ version:/{print $2; exit}' | sed 's/(.*//')"
|
|
213
|
-
if [ -z "$CLI_PIN" ]; then
|
|
214
|
-
echo "::error::Could not read the resolved @indigoai-us/hq-cloud version from hq-cli's pnpm-lock.yaml. The lockfile layout changed — fix this parser rather than letting the check pass silently."
|
|
215
|
-
exit 1
|
|
216
|
-
fi
|
|
217
|
-
echo "hq-cli pins: ${CLI_PIN}"
|
|
218
|
-
|
|
219
|
-
# --- hq-desktop-app: HQ_CLOUD_VERSION constant, tilde stripped ---
|
|
220
|
-
gh api "repos/indigoai-us/hq-desktop-app/contents/crates/hq-desktop-core/src/hq_cloud.rs?ref=main" \
|
|
221
|
-
-H 'Accept: application/vnd.github.raw' > /tmp/hq_cloud.rs
|
|
222
|
-
DESK_RAW="$(grep -oP 'HQ_CLOUD_VERSION\s*:\s*&str\s*=\s*"\K[^"]+' /tmp/hq_cloud.rs | head -1)"
|
|
223
|
-
if [ -z "$DESK_RAW" ]; then
|
|
224
|
-
echo "::error::Could not read HQ_CLOUD_VERSION from hq-desktop-app's crates/hq-desktop-core/src/hq_cloud.rs. The constant moved or was renamed — fix this parser rather than letting the check pass silently."
|
|
225
|
-
exit 1
|
|
226
|
-
fi
|
|
227
|
-
DESK_PIN="${DESK_RAW#\~}"
|
|
228
|
-
DESK_PIN="${DESK_PIN#^}"
|
|
229
|
-
echo "hq-desktop-app pins: ${DESK_RAW} (compared as ${DESK_PIN})"
|
|
230
|
-
|
|
231
|
-
LAGGARDS=""
|
|
232
|
-
if [ "$CLI_PIN" != "$LATEST" ]; then
|
|
233
|
-
LAGGARDS="${LAGGARDS}"$'\n'"• \`hq-cli\` is on \`${CLI_PIN}\` (pnpm-lock.yaml)"
|
|
234
|
-
fi
|
|
235
|
-
if [ "$DESK_PIN" != "$LATEST" ]; then
|
|
236
|
-
LAGGARDS="${LAGGARDS}"$'\n'"• \`hq-desktop-app\` is on \`${DESK_RAW}\` (HQ_CLOUD_VERSION)"
|
|
237
|
-
fi
|
|
238
|
-
|
|
239
|
-
if [ -z "$LAGGARDS" ]; then
|
|
240
|
-
echo "Both consumers are pinned to ${LATEST}. Nothing to nag about."
|
|
241
|
-
exit 0
|
|
242
|
-
fi
|
|
243
|
-
|
|
244
|
-
TEXT=":link: *hq-cloud \`${LATEST}\` is released but not pinned everywhere.* A runner nobody pins is a runner nobody runs:${LAGGARDS}"$'\n'"Bump the pin(s) to \`${LATEST}\` and ship. <https://github.com/indigoai-us/hq-cloud/releases/tag/${LATEST_TAG}|Release ${LATEST_TAG}>."
|
|
245
|
-
|
|
246
|
-
RESP="$(curl -s -X POST \
|
|
247
|
-
-H "Authorization: Bearer ${SLACK_TOKEN}" \
|
|
248
|
-
-H 'Content-type: application/json; charset=utf-8' \
|
|
249
|
-
--data "$(jq -nc --arg c "$SLACK_CHANNEL" --arg t "$TEXT" '{channel:$c,text:$t,unfurl_links:false}')" \
|
|
250
|
-
https://slack.com/api/chat.postMessage)"
|
|
251
|
-
OK="$(echo "$RESP" | jq -r '.ok')"
|
|
252
|
-
echo "Slack post ok=${OK} err=$(echo "$RESP" | jq -r '.error // "-"')"
|
|
253
|
-
if [ "$OK" != "true" ]; then
|
|
254
|
-
echo "::error::Slack rejected the nag: $(echo "$RESP" | jq -r '.error // "unknown"')"
|
|
255
|
-
exit 1
|
|
256
|
-
fi
|
package/eslint.config.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import js from "@eslint/js";
|
|
2
|
-
import tseslint from "typescript-eslint";
|
|
3
|
-
|
|
4
|
-
const nodeGlobals = {
|
|
5
|
-
AbortController: "readonly",
|
|
6
|
-
Buffer: "readonly",
|
|
7
|
-
clearInterval: "readonly",
|
|
8
|
-
clearTimeout: "readonly",
|
|
9
|
-
console: "readonly",
|
|
10
|
-
fetch: "readonly",
|
|
11
|
-
process: "readonly",
|
|
12
|
-
setInterval: "readonly",
|
|
13
|
-
setTimeout: "readonly",
|
|
14
|
-
structuredClone: "readonly",
|
|
15
|
-
URL: "readonly",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const vitestGlobals = {
|
|
19
|
-
afterEach: "readonly",
|
|
20
|
-
beforeEach: "readonly",
|
|
21
|
-
describe: "readonly",
|
|
22
|
-
expect: "readonly",
|
|
23
|
-
it: "readonly",
|
|
24
|
-
vi: "readonly",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export default tseslint.config(
|
|
28
|
-
{
|
|
29
|
-
ignores: [
|
|
30
|
-
"coverage/**",
|
|
31
|
-
"dist/**",
|
|
32
|
-
"node_modules/**",
|
|
33
|
-
"pnpm-lock.yaml",
|
|
34
|
-
"*.config.js",
|
|
35
|
-
"*.config.mjs",
|
|
36
|
-
"*.config.cjs",
|
|
37
|
-
"generated/**",
|
|
38
|
-
"**/*.generated.*",
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
files: ["src/**/*.ts", "test/**/*.ts"],
|
|
43
|
-
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
44
|
-
languageOptions: {
|
|
45
|
-
ecmaVersion: 2022,
|
|
46
|
-
sourceType: "module",
|
|
47
|
-
globals: {
|
|
48
|
-
...nodeGlobals,
|
|
49
|
-
...vitestGlobals,
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
rules: {
|
|
53
|
-
"@typescript-eslint/no-explicit-any": "warn",
|
|
54
|
-
"@typescript-eslint/no-unused-vars": [
|
|
55
|
-
"error",
|
|
56
|
-
{
|
|
57
|
-
argsIgnorePattern: "^_",
|
|
58
|
-
caughtErrorsIgnorePattern: "^_",
|
|
59
|
-
destructuredArrayIgnorePattern: "^_",
|
|
60
|
-
varsIgnorePattern: "^_",
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
"no-useless-assignment": "off",
|
|
64
|
-
"preserve-caught-error": "off",
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
);
|
package/pnpm-workspace.yaml
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live E2E for the presigned-URL transport (hq-cloud feat/presign-transport).
|
|
3
|
-
*
|
|
4
|
-
* Drives the real s3.ts primitives through the PRESIGN factory against
|
|
5
|
-
* production (hqapi.hq.computer), exercising the exact code path that
|
|
6
|
-
* @getindigo.ai sync sessions will use: VaultClient.presign/listFiles +
|
|
7
|
-
* PresignObjectIO + fetch. Creates a scratch object under a clearly-marked
|
|
8
|
-
* prefix, round-trips it (put → list → head → get → symlink → delete), and
|
|
9
|
-
* deletes everything it created. Read-only on all real vault data.
|
|
10
|
-
*
|
|
11
|
-
* Run from the worktree root after `pnpm build`:
|
|
12
|
-
* node scripts/presign-transport-e2e.mjs
|
|
13
|
-
*/
|
|
14
|
-
import * as fs from "fs";
|
|
15
|
-
import * as os from "os";
|
|
16
|
-
import * as path from "path";
|
|
17
|
-
import { VaultClient } from "../dist/vault-client.js";
|
|
18
|
-
import {
|
|
19
|
-
setObjectIOFactory,
|
|
20
|
-
presignObjectIOFactory,
|
|
21
|
-
} from "../dist/object-io.js";
|
|
22
|
-
import {
|
|
23
|
-
uploadFile,
|
|
24
|
-
uploadSymlink,
|
|
25
|
-
downloadFile,
|
|
26
|
-
listRemoteFiles,
|
|
27
|
-
headRemoteFile,
|
|
28
|
-
deleteRemoteFile,
|
|
29
|
-
} from "../dist/s3.js";
|
|
30
|
-
|
|
31
|
-
const API = process.env.HQ_VAULT_API_URL ?? "https://hqapi.hq.computer";
|
|
32
|
-
|
|
33
|
-
const results = [];
|
|
34
|
-
function check(name, cond, detail = "") {
|
|
35
|
-
results.push({ name, ok: !!cond, detail });
|
|
36
|
-
console.log(`${cond ? "✓" : "✗"} ${name}${detail ? ` — ${detail}` : ""}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function loadAccessToken() {
|
|
40
|
-
const p = path.join(os.homedir(), ".hq", "cognito-tokens.json");
|
|
41
|
-
const t = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
42
|
-
const at = t.accessToken;
|
|
43
|
-
if (!at) throw new Error("no accessToken in cognito-tokens.json");
|
|
44
|
-
return at;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function main() {
|
|
48
|
-
const token = loadAccessToken();
|
|
49
|
-
const vault = new VaultClient({ apiUrl: API, authToken: token });
|
|
50
|
-
|
|
51
|
-
// Resolve a company the caller can write to (owner/admin role).
|
|
52
|
-
const memberships = await vault.listMyMemberships();
|
|
53
|
-
const writable = memberships.find(
|
|
54
|
-
(m) => m.role === "owner" || m.role === "admin",
|
|
55
|
-
);
|
|
56
|
-
if (!writable) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
`no owner/admin membership found (roles: ${memberships.map((m) => m.role).join(",") || "none"})`,
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
const companyUid = writable.companyUid;
|
|
62
|
-
let slug = companyUid;
|
|
63
|
-
try {
|
|
64
|
-
slug = (await vault.entity.get(companyUid)).slug ?? companyUid;
|
|
65
|
-
} catch {
|
|
66
|
-
/* display only */
|
|
67
|
-
}
|
|
68
|
-
console.log(`\nTarget: ${slug} (${companyUid}), role=${writable.role}\n`);
|
|
69
|
-
|
|
70
|
-
// Install the presign transport — the same selection runRunner makes for a
|
|
71
|
-
// @getindigo.ai session. ctx.credentials/bucketName/region are UNUSED on the
|
|
72
|
-
// presign path (only ctx.uid → companyUid matters), so dummy values are fine.
|
|
73
|
-
setObjectIOFactory(presignObjectIOFactory(vault));
|
|
74
|
-
const ctx = {
|
|
75
|
-
uid: companyUid,
|
|
76
|
-
slug,
|
|
77
|
-
bucketName: "(presign-unused)",
|
|
78
|
-
region: "us-east-1",
|
|
79
|
-
credentials: { accessKeyId: "x", secretAccessKey: "x", sessionToken: "x" },
|
|
80
|
-
expiresAt: "2099-01-01T00:00:00.000Z",
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const stamp = Date.now().toString(36);
|
|
84
|
-
const prefix = `shared/_presign_e2e_${stamp}/`;
|
|
85
|
-
const fileKey = `${prefix}hooks/presign-e2e-hook.sh`;
|
|
86
|
-
const legacyKey = `${prefix}hooks/legacy-e2e-hook.sh`;
|
|
87
|
-
const linkKey = `${prefix}link`;
|
|
88
|
-
const author = { userSub: "e2e-probe-sub", email: "hassaan@getindigo.ai" };
|
|
89
|
-
|
|
90
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "presign-e2e-"));
|
|
91
|
-
const localUp = path.join(tmp, "presign-e2e-hook.sh");
|
|
92
|
-
const localDown = path.join(tmp, "downloaded-hook.sh");
|
|
93
|
-
const legacyDown = path.join(tmp, "legacy-hook.sh");
|
|
94
|
-
const localLink = path.join(tmp, "link");
|
|
95
|
-
const content = `#!/usr/bin/env bash\n# presign-transport probe ${stamp}\n${"x".repeat(2000)}`;
|
|
96
|
-
fs.writeFileSync(localUp, content);
|
|
97
|
-
fs.chmodSync(localUp, 0o755);
|
|
98
|
-
|
|
99
|
-
let allCreated = [];
|
|
100
|
-
try {
|
|
101
|
-
// ---- PUT (presigned, with metadata signed in) -----------------------
|
|
102
|
-
const up = await uploadFile(ctx, localUp, fileKey, author);
|
|
103
|
-
allCreated.push(fileKey);
|
|
104
|
-
check("uploadFile returns an etag", !!up.etag, up.etag);
|
|
105
|
-
|
|
106
|
-
// ---- LIST (presigned list, etag is load-bearing) --------------------
|
|
107
|
-
const listed = await listRemoteFiles(ctx, prefix);
|
|
108
|
-
const row = listed.find((o) => o.key === fileKey);
|
|
109
|
-
check("listRemoteFiles finds the uploaded key", !!row);
|
|
110
|
-
check(
|
|
111
|
-
"listed object carries a non-empty etag (#269)",
|
|
112
|
-
!!row && typeof row.etag === "string" && row.etag.length > 0,
|
|
113
|
-
row?.etag,
|
|
114
|
-
);
|
|
115
|
-
check(
|
|
116
|
-
"listed size matches uploaded bytes",
|
|
117
|
-
!!row && row.size === Buffer.byteLength(content),
|
|
118
|
-
`${row?.size} vs ${Buffer.byteLength(content)}`,
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
// ---- HEAD (presigned GET + header read) -----------------------------
|
|
122
|
-
const head = await headRemoteFile(ctx, fileKey);
|
|
123
|
-
check("headRemoteFile returns metadata", !!head && !!head.metadata);
|
|
124
|
-
check(
|
|
125
|
-
"metadata.created-by round-trips (signed PUT meta)",
|
|
126
|
-
head?.metadata?.["created-by"] === author.email,
|
|
127
|
-
head?.metadata?.["created-by"],
|
|
128
|
-
);
|
|
129
|
-
check(
|
|
130
|
-
"metadata.hq-mode round-trips as 755",
|
|
131
|
-
head?.metadata?.["hq-mode"] === "755",
|
|
132
|
-
head?.metadata?.["hq-mode"],
|
|
133
|
-
);
|
|
134
|
-
check(
|
|
135
|
-
"head etag matches list etag",
|
|
136
|
-
!!head && !!row && head.etag === row.etag,
|
|
137
|
-
`${head?.etag} vs ${row?.etag}`,
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
// ---- GET (presigned download to an empty destination, bytes + mode) --
|
|
141
|
-
const dl = await downloadFile(ctx, fileKey, localDown);
|
|
142
|
-
const got = fs.readFileSync(localDown, "utf-8");
|
|
143
|
-
check("downloadFile bytes match upload", got === content);
|
|
144
|
-
check(
|
|
145
|
-
"downloaded hook mode is 0755 (hq-mode applied)",
|
|
146
|
-
(fs.lstatSync(localDown).mode & 0o777) === 0o755,
|
|
147
|
-
"0" + (fs.lstatSync(localDown).mode & 0o777).toString(8),
|
|
148
|
-
);
|
|
149
|
-
check(
|
|
150
|
-
"downloadFile surfaces created-by metadata",
|
|
151
|
-
dl?.metadata?.["created-by"] === author.email,
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
// ---- Legacy hq-mode fallback ----------------------------------------
|
|
155
|
-
// Create a pre-hq-mode object through the same real presigned PUT
|
|
156
|
-
// endpoint, deliberately omitting metadata. A legacy remote overwrite
|
|
157
|
-
// must retain the known-good local executable mode instead of silently
|
|
158
|
-
// replacing the hook with the receiver's usual 0644 umask default.
|
|
159
|
-
const legacyPresign = await vault.presign({
|
|
160
|
-
companyUid,
|
|
161
|
-
op: "put",
|
|
162
|
-
keys: [{ key: legacyKey, op: "put", contentType: "text/x-shellscript" }],
|
|
163
|
-
});
|
|
164
|
-
const legacyPut = legacyPresign.results[0];
|
|
165
|
-
if (!legacyPut?.url) throw new Error("legacy PUT presign was not returned");
|
|
166
|
-
const legacyPutResponse = await fetch(legacyPut.url, {
|
|
167
|
-
method: "PUT",
|
|
168
|
-
headers: legacyPut.headers,
|
|
169
|
-
body: "#!/usr/bin/env bash\necho legacy hook\n",
|
|
170
|
-
});
|
|
171
|
-
if (!legacyPutResponse.ok) {
|
|
172
|
-
throw new Error(`legacy presigned PUT failed: ${legacyPutResponse.status}`);
|
|
173
|
-
}
|
|
174
|
-
allCreated.push(legacyKey);
|
|
175
|
-
const legacyHead = await headRemoteFile(ctx, legacyKey);
|
|
176
|
-
check(
|
|
177
|
-
"legacy object deliberately has no hq-mode metadata",
|
|
178
|
-
legacyHead?.metadata?.["hq-mode"] === undefined,
|
|
179
|
-
legacyHead?.metadata?.["hq-mode"],
|
|
180
|
-
);
|
|
181
|
-
fs.writeFileSync(legacyDown, "#!/usr/bin/env bash\necho prior hook\n");
|
|
182
|
-
fs.chmodSync(legacyDown, 0o755);
|
|
183
|
-
const legacyDownload = await downloadFile(ctx, legacyKey, legacyDown);
|
|
184
|
-
check(
|
|
185
|
-
"legacy hook fallback retains existing 0755 mode (not 0644)",
|
|
186
|
-
(fs.lstatSync(legacyDown).mode & 0o777) === 0o755,
|
|
187
|
-
"0" + (fs.lstatSync(legacyDown).mode & 0o777).toString(8),
|
|
188
|
-
);
|
|
189
|
-
check(
|
|
190
|
-
"legacy hq-mode fallback is surfaced for repair telemetry",
|
|
191
|
-
legacyDownload.modeWarnings?.some(
|
|
192
|
-
(warning) =>
|
|
193
|
-
warning.reason === "missing-hq-mode" &&
|
|
194
|
-
warning.fallback === "preserved-local-mode",
|
|
195
|
-
) === true,
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
// ---- SYMLINK round-trip --------------------------------------------
|
|
199
|
-
const ln = await uploadSymlink(ctx, "../target/path.txt", linkKey, author);
|
|
200
|
-
allCreated.push(linkKey);
|
|
201
|
-
check("uploadSymlink returns an etag", !!ln.etag);
|
|
202
|
-
await downloadFile(ctx, linkKey, localLink);
|
|
203
|
-
const st = fs.lstatSync(localLink);
|
|
204
|
-
check("downloaded symlink is a symlink", st.isSymbolicLink());
|
|
205
|
-
check(
|
|
206
|
-
"symlink target round-trips",
|
|
207
|
-
st.isSymbolicLink() && fs.readlinkSync(localLink) === "../target/path.txt",
|
|
208
|
-
st.isSymbolicLink() ? fs.readlinkSync(localLink) : "(not a link)",
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
// ---- DELETE (presigned) --------------------------------------------
|
|
212
|
-
await deleteRemoteFile(ctx, fileKey);
|
|
213
|
-
await deleteRemoteFile(ctx, legacyKey);
|
|
214
|
-
await deleteRemoteFile(ctx, linkKey);
|
|
215
|
-
allCreated = [];
|
|
216
|
-
const afterFile = await headRemoteFile(ctx, fileKey);
|
|
217
|
-
check("headRemoteFile is null after delete", afterFile === null);
|
|
218
|
-
const afterList = await listRemoteFiles(ctx, prefix);
|
|
219
|
-
check(
|
|
220
|
-
"listRemoteFiles empty after delete",
|
|
221
|
-
afterList.filter((o) => o.key.startsWith(prefix)).length === 0,
|
|
222
|
-
`${afterList.length} remaining`,
|
|
223
|
-
);
|
|
224
|
-
} finally {
|
|
225
|
-
// Best-effort cleanup of anything left if an assertion threw mid-run.
|
|
226
|
-
for (const k of allCreated) {
|
|
227
|
-
try {
|
|
228
|
-
await deleteRemoteFile(ctx, k);
|
|
229
|
-
} catch {
|
|
230
|
-
/* ignore */
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
fs.rmSync(tmp, { recursive: true, force: true });
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const failed = results.filter((r) => !r.ok);
|
|
237
|
-
console.log(
|
|
238
|
-
`\n${results.length - failed.length}/${results.length} checks passed`,
|
|
239
|
-
);
|
|
240
|
-
if (failed.length > 0) {
|
|
241
|
-
console.log("FAILED:", failed.map((f) => f.name).join("; "));
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
console.log("ALL GREEN");
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
main().catch((err) => {
|
|
248
|
-
console.error("E2E error:", err);
|
|
249
|
-
process.exit(2);
|
|
250
|
-
});
|