misonL commited on
Commit
4d6d6c8
·
verified ·
1 Parent(s): 0083686

Deploy fa03242 to Docker Space

Browse files

Source: MisonL/gpt-image-playground-customer@fa03242bd6b72fa8fe64e177f690cb8f81ba2a8c

public/hf-space-deploy-marker.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "schema_version": 1,
3
+ "local_sha": "fa03242bd6b72fa8fe64e177f690cb8f81ba2a8c",
4
+ "created_at": "2026-06-20T10:52:58.463Z"
5
+ }
scripts/agent-doctor.mjs CHANGED
@@ -350,7 +350,7 @@ function buildSummary({ capabilities, runtime, contract, smoke }) {
350
  capabilities.ok && capabilities.body?.agent_streaming?.page_sse?.auth?.required === true
351
  ? Boolean(process.env.GPT_IMAGE_APP_PASSWORD_HASH)
352
  : capabilities.ok,
353
- page_sse_real_smoke: summarizeSmokeCheck(smoke, PAGE_SSE_GENERATE_SMOKE_NAME),
354
  responses_page_sse_generate_smoke: summarizeSmokeCheck(smoke, PAGE_SSE_GENERATE_SMOKE_NAME),
355
  real_smoke_checks: summarizeSmokeChecks(smoke),
356
  responses_gpt2image_ready:
@@ -371,6 +371,13 @@ function summarizeSmokeCheck(smoke, name) {
371
  return check.ok ? 'passed' : 'failed';
372
  }
373
 
 
 
 
 
 
 
 
374
  function summarizeSmokeChecks(smoke) {
375
  return {
376
  agent_generate_1k: summarizeSmokeCheck(smoke, 'generate_1k'),
 
350
  capabilities.ok && capabilities.body?.agent_streaming?.page_sse?.auth?.required === true
351
  ? Boolean(process.env.GPT_IMAGE_APP_PASSWORD_HASH)
352
  : capabilities.ok,
353
+ page_sse_real_smoke: summarizePageSseSmoke(smoke),
354
  responses_page_sse_generate_smoke: summarizeSmokeCheck(smoke, PAGE_SSE_GENERATE_SMOKE_NAME),
355
  real_smoke_checks: summarizeSmokeChecks(smoke),
356
  responses_gpt2image_ready:
 
371
  return check.ok ? 'passed' : 'failed';
372
  }
373
 
374
+ function summarizePageSseSmoke(smoke) {
375
+ const states = [PAGE_SSE_GENERATE_SMOKE_NAME, 'page_sse_edit_2k'].map((name) => summarizeSmokeCheck(smoke, name));
376
+ if (states.includes('failed')) return 'failed';
377
+ if (states.includes('passed')) return 'passed';
378
+ return 'skipped';
379
+ }
380
+
381
  function summarizeSmokeChecks(smoke) {
382
  return {
383
  agent_generate_1k: summarizeSmokeCheck(smoke, 'generate_1k'),
scripts/command-center.test.mjs CHANGED
@@ -882,6 +882,96 @@ describe('Command center scripts', () => {
882
  );
883
  });
884
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
885
  it('prints localized agent:doctor help and option errors', async () => {
886
  const help = await runNodeCommandAsync(['scripts/agent-doctor.mjs', '--help'], {
887
  env: process.env,
 
882
  );
883
  });
884
 
885
+ it('reports failed agent:doctor page SSE smoke checks without hiding the failure', async () => {
886
+ await withServer(
887
+ (request, response) => {
888
+ if (request.url === '/api/agent/capabilities') {
889
+ response.writeHead(200, { 'content-type': 'application/json' });
890
+ response.end(
891
+ JSON.stringify({
892
+ defaults: { state_backend: 'memory' },
893
+ storage: { image_storage_mode: 'indexeddb', postgres_configured: false },
894
+ agent_streaming: { page_sse: { supported: true } },
895
+ agent_jobs: { supported: true },
896
+ routing_rules: {},
897
+ supported: {
898
+ image_backend_requirements: {
899
+ 'responses-image-generation': {
900
+ supported: true,
901
+ enabled: true,
902
+ missing_env: []
903
+ }
904
+ }
905
+ }
906
+ })
907
+ );
908
+ return;
909
+ }
910
+ if (request.url === '/api/runtime-capabilities') {
911
+ response.writeHead(200, { 'content-type': 'application/json' });
912
+ response.end(
913
+ JSON.stringify({
914
+ streaming: {
915
+ defaultMode: 'auto',
916
+ unavailableMarkScope: 'channel+backend+strategy+operation'
917
+ },
918
+ streamingBatch: { enabled: true },
919
+ responsesImageBackend: { enabled: true, mode: 'experimental' }
920
+ })
921
+ );
922
+ return;
923
+ }
924
+ if (request.url === '/api/agent/images/generate') {
925
+ response.writeHead(200, { 'content-type': 'application/json' });
926
+ response.end(JSON.stringify({ images: [{ id: 'doctor-generate', filename: 'doctor.png' }] }));
927
+ return;
928
+ }
929
+ if (request.url === '/api/images') {
930
+ response.writeHead(503, { 'content-type': 'text/plain' });
931
+ response.end('503 Service temporarily unavailable');
932
+ return;
933
+ }
934
+ if (request.url === '/api/agent/jobs/images/generate') {
935
+ response.writeHead(400, { 'content-type': 'application/json' });
936
+ response.end(
937
+ JSON.stringify({
938
+ error: {
939
+ code: 'idempotency_key_required',
940
+ message: 'missing key',
941
+ retryable: false
942
+ }
943
+ })
944
+ );
945
+ return;
946
+ }
947
+ response.writeHead(404, { 'content-type': 'application/json' });
948
+ response.end(JSON.stringify({ error: 'missing' }));
949
+ },
950
+ async (baseUrl) => {
951
+ const result = await runNodeCommandAsync(
952
+ ['scripts/agent-doctor.mjs', '--base-url', baseUrl, '--allow-billable'],
953
+ { timeoutMs: 15_000 }
954
+ );
955
+
956
+ assert.equal(result.ok, false);
957
+ const body = parseJsonPayload(result.stdout, 'agent doctor');
958
+ assert.equal(body.ok, false);
959
+ assert.equal(body.summary.billable_smoke, 'failed');
960
+ assert.equal(body.summary.page_sse_real_smoke, 'failed');
961
+ assert.equal(body.summary.responses_page_sse_generate_smoke, 'failed');
962
+ assert.equal(body.summary.real_smoke_checks.agent_generate_1k, 'passed');
963
+ assert.equal(body.summary.real_smoke_checks.responses_page_sse_generate_1k, 'failed');
964
+ assert.equal(body.summary.real_smoke_checks.page_sse_edit_2k, 'skipped');
965
+ assert.match(
966
+ body.layers
967
+ .find((layer) => layer.name === 'billable_smoke')
968
+ .checks.find((check) => check.name === 'responses_page_sse_generate_1k').output,
969
+ /503 Service temporarily unavailable/
970
+ );
971
+ }
972
+ );
973
+ });
974
+
975
  it('prints localized agent:doctor help and option errors', async () => {
976
  const help = await runNodeCommandAsync(['scripts/agent-doctor.mjs', '--help'], {
977
  env: process.env,
scripts/deploy-hf-space.mjs CHANGED
@@ -1,7 +1,7 @@
1
  #!/usr/bin/env node
2
 
3
  import { spawnSync } from 'node:child_process';
4
- import { mkdtempSync, rmSync } from 'node:fs';
5
  import { tmpdir } from 'node:os';
6
  import { join } from 'node:path';
7
  import { setTimeout as delay } from 'node:timers/promises';
@@ -13,6 +13,8 @@ const STATUS_POLL_ATTEMPTS = 40;
13
  const STATUS_POLL_INTERVAL_MS = 10_000;
14
  const PUBLIC_ENDPOINT_TIMEOUT_MS = 10_000;
15
  const HF_CLI_TIMEOUT_MS = 120_000;
 
 
16
  export const GIT_ARCHIVE_MAX_BUFFER_BYTES = 256 * 1024 * 1024;
17
 
18
  function parseArgs(argv) {
@@ -138,8 +140,30 @@ export function findRemoteDeletePaths(localFiles, remoteFiles) {
138
  return [...remoteFiles].filter((filename) => !localFiles.has(filename)).sort();
139
  }
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  function uploadSourceTree(sourceDir, localSha) {
142
- const deletePaths = findRemoteDeletePaths(readLocalGitFiles(), readRemoteFilePaths());
 
143
  const output = runText('hf', buildUploadArgs({ sourceDir, localSha, repoSlug: readRepositorySlug(), deletePaths }));
144
  return extractUploadCommitSha(output);
145
  }
@@ -149,7 +173,7 @@ function readSpaceInfo() {
149
  return parseJsonPayload(output, 'hf spaces info');
150
  }
151
 
152
- async function waitForRunning(spaceCommitSha) {
153
  let lastStage = 'unknown';
154
  let lastSha = 'unknown';
155
  for (let attempt = 1; attempt <= STATUS_POLL_ATTEMPTS; attempt += 1) {
@@ -162,13 +186,33 @@ async function waitForRunning(spaceCommitSha) {
162
  }
163
  await delay(STATUS_POLL_INTERVAL_MS);
164
  }
165
- throw new Error(`Space did not reach RUNNING for ${spaceCommitSha}; last stage=${lastStage} sha=${lastSha}`);
 
 
 
 
 
 
 
 
166
  }
167
 
168
  async function fetchJson(path) {
169
  return fetchJsonWithTimeout(new URL(path, HF_SPACE_URL), { timeoutMs: PUBLIC_ENDPOINT_TIMEOUT_MS });
170
  }
171
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  async function verifyPublicEndpoints() {
173
  const authStatus = await fetchJson('/api/auth-status');
174
  const capabilities = await fetchJson('/api/agent/capabilities');
@@ -199,7 +243,7 @@ async function deploy() {
199
  const sourceDir = prepareSourceTree();
200
  try {
201
  const spaceCommitSha = uploadSourceTree(sourceDir, localSha);
202
- const runtime = await waitForRunning(spaceCommitSha);
203
  const verification = await verifyPublicEndpoints();
204
  console.log(
205
  JSON.stringify(
 
1
  #!/usr/bin/env node
2
 
3
  import { spawnSync } from 'node:child_process';
4
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
5
  import { tmpdir } from 'node:os';
6
  import { join } from 'node:path';
7
  import { setTimeout as delay } from 'node:timers/promises';
 
13
  const STATUS_POLL_INTERVAL_MS = 10_000;
14
  const PUBLIC_ENDPOINT_TIMEOUT_MS = 10_000;
15
  const HF_CLI_TIMEOUT_MS = 120_000;
16
+ const DEPLOY_MARKER_REPO_PATH = 'public/hf-space-deploy-marker.json';
17
+ const DEPLOY_MARKER_PUBLIC_PATH = '/hf-space-deploy-marker.json';
18
  export const GIT_ARCHIVE_MAX_BUFFER_BYTES = 256 * 1024 * 1024;
19
 
20
  function parseArgs(argv) {
 
140
  return [...remoteFiles].filter((filename) => !localFiles.has(filename)).sort();
141
  }
142
 
143
+ export function buildDeployMarker(localSha, createdAt = new Date()) {
144
+ if (!/^[0-9a-f]{40}$/.test(String(localSha || ''))) throw new Error('localSha must be a full git commit SHA.');
145
+ return {
146
+ schema_version: 1,
147
+ local_sha: localSha,
148
+ created_at: createdAt.toISOString()
149
+ };
150
+ }
151
+
152
+ function writeDeployMarker(sourceDir, marker) {
153
+ const markerPath = join(sourceDir, DEPLOY_MARKER_REPO_PATH);
154
+ mkdirSync(join(sourceDir, 'public'), { recursive: true });
155
+ writeFileSync(markerPath, `${JSON.stringify(marker, null, 2)}\n`, 'utf8');
156
+ }
157
+
158
+ function readLocalGitFilesWithDeployMarker() {
159
+ const files = readLocalGitFiles();
160
+ files.add(DEPLOY_MARKER_REPO_PATH);
161
+ return files;
162
+ }
163
+
164
  function uploadSourceTree(sourceDir, localSha) {
165
+ writeDeployMarker(sourceDir, buildDeployMarker(localSha));
166
+ const deletePaths = findRemoteDeletePaths(readLocalGitFilesWithDeployMarker(), readRemoteFilePaths());
167
  const output = runText('hf', buildUploadArgs({ sourceDir, localSha, repoSlug: readRepositorySlug(), deletePaths }));
168
  return extractUploadCommitSha(output);
169
  }
 
173
  return parseJsonPayload(output, 'hf spaces info');
174
  }
175
 
176
+ async function waitForRunning(spaceCommitSha, localSha) {
177
  let lastStage = 'unknown';
178
  let lastSha = 'unknown';
179
  for (let attempt = 1; attempt <= STATUS_POLL_ATTEMPTS; attempt += 1) {
 
186
  }
187
  await delay(STATUS_POLL_INTERVAL_MS);
188
  }
189
+ const marker = await verifyDeployMarker(localSha);
190
+ return {
191
+ stage: lastStage,
192
+ sha: lastSha,
193
+ management_status: 'runtime_stage_not_running',
194
+ service_marker_verified: true,
195
+ warning: `Space did not reach RUNNING for ${spaceCommitSha}; last stage=${lastStage} sha=${lastSha}`,
196
+ marker
197
+ };
198
  }
199
 
200
  async function fetchJson(path) {
201
  return fetchJsonWithTimeout(new URL(path, HF_SPACE_URL), { timeoutMs: PUBLIC_ENDPOINT_TIMEOUT_MS });
202
  }
203
 
204
+ async function verifyDeployMarker(localSha) {
205
+ const marker = await fetchJson(`${DEPLOY_MARKER_PUBLIC_PATH}?t=${Date.now()}`);
206
+ if (marker?.schema_version !== 1) throw new Error('deploy marker schema_version was not 1.');
207
+ if (marker.local_sha !== localSha) {
208
+ throw new Error(`deploy marker local_sha mismatch: expected ${localSha}, received ${marker.local_sha || 'missing'}.`);
209
+ }
210
+ if (typeof marker.created_at !== 'string' || !marker.created_at.trim()) {
211
+ throw new Error('deploy marker created_at was missing.');
212
+ }
213
+ return marker;
214
+ }
215
+
216
  async function verifyPublicEndpoints() {
217
  const authStatus = await fetchJson('/api/auth-status');
218
  const capabilities = await fetchJson('/api/agent/capabilities');
 
243
  const sourceDir = prepareSourceTree();
244
  try {
245
  const spaceCommitSha = uploadSourceTree(sourceDir, localSha);
246
+ const runtime = await waitForRunning(spaceCommitSha, localSha);
247
  const verification = await verifyPublicEndpoints();
248
  console.log(
249
  JSON.stringify(
scripts/deploy-hf-space.test.mjs CHANGED
@@ -3,6 +3,7 @@ import { describe, it } from 'node:test';
3
 
4
  import {
5
  GIT_ARCHIVE_MAX_BUFFER_BYTES,
 
6
  buildUploadArgs,
7
  extractUploadCommitSha,
8
  findRemoteDeletePaths,
@@ -125,4 +126,30 @@ describe('HF Space deploy script', () => {
125
  ['old.md']
126
  );
127
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  });
 
3
 
4
  import {
5
  GIT_ARCHIVE_MAX_BUFFER_BYTES,
6
+ buildDeployMarker,
7
  buildUploadArgs,
8
  extractUploadCommitSha,
9
  findRemoteDeletePaths,
 
126
  ['old.md']
127
  );
128
  });
129
+
130
+ it('builds a non-secret deploy marker for service-side deployment verification', () => {
131
+ const marker = buildDeployMarker('3333333333333333333333333333333333333333', new Date('2026-06-20T10:00:00.000Z'));
132
+
133
+ assert.deepEqual(marker, {
134
+ schema_version: 1,
135
+ local_sha: '3333333333333333333333333333333333333333',
136
+ created_at: '2026-06-20T10:00:00.000Z'
137
+ });
138
+ assert.doesNotMatch(JSON.stringify(marker), /key|token|secret|password/i);
139
+ });
140
+
141
+ it('rejects short deploy marker commit shas before upload', () => {
142
+ assert.throws(() => buildDeployMarker('3333333'), /full git commit SHA/);
143
+ });
144
+
145
+ it('keeps the generated deploy marker from being deleted on upload', () => {
146
+ assert.deepEqual(
147
+ findRemoteDeletePaths(new Set(['README.md', 'public/hf-space-deploy-marker.json']), [
148
+ 'README.md',
149
+ 'public/hf-space-deploy-marker.json',
150
+ 'old.md'
151
+ ]),
152
+ ['old.md']
153
+ );
154
+ });
155
  });