fix(mindspace): release edited public pages and fix achievements dates
Memind CI / Test, build, and release guards (push) Successful in 3m37s

Finish now releases static HTML delivery contracts in a finally block so
re-edited pages are not stuck at HTTP 409, and M成果 groups pages by
Asia/Shanghai calendar dates to avoid duplicate day headings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-13 06:41:40 +08:00
parent fdc234e5d0
commit 53b0d2c62f
6 changed files with 222 additions and 26 deletions
+21 -5
View File
@@ -13,6 +13,7 @@ import {
import {
markPageDeliveryContractReady,
preparePageDeliveryContract,
releaseMaterializedPageDeliveryContracts,
} from '../mindspace-delivery-contract.mjs';
import { maybeRepairH5HtmlAfterFinish } from '../mindspace-h5-html-finish-guard.mjs';
import { maybeRepairPageDataAfterFinish } from '../mindspace-page-data-finish-guard.mjs';
@@ -79,6 +80,8 @@ export function attachPortalSessionRoutes(
maybeRepairPageDataAfterFinish,
markPageDeliveryContractReadyFn =
markPageDeliveryContractReady,
releaseMaterializedPageDeliveryContractsFn =
releaseMaterializedPageDeliveryContracts,
finishDeliveryRetryDelaysMs = [250, 1_000],
finishDeliveryRetryWaitFn = (delayMs) =>
new Promise((resolve) =>
@@ -477,6 +480,8 @@ export function attachPortalSessionRoutes(
// workspace from DB-backed assets only.
const finalizeAfterFinishOnce = async (sid, uid) => {
beginSessionPageDelivery(sid);
let releaseCandidatePaths = [];
let allowPgRequiredRelease = false;
try {
const apiFetchFn = async (pathname, init) => {
const target = await tkmindProxy.resolveTarget(sid);
@@ -616,6 +621,8 @@ export function attachPortalSessionRoutes(
...deliveryContractWrites.keys(),
]),
].sort();
releaseCandidatePaths = publicHtmlRelativePaths;
allowPgRequiredRelease = htmlReady && pageDataReady;
const pgRequired = [
...(Array.isArray(messages) ? messages : []),
].some(
@@ -660,11 +667,6 @@ export function attachPortalSessionRoutes(
pgRequired,
});
}
await markPageDeliveryContractReadyFn({
pool: authPool,
userId: uid,
relativePath,
}).catch(() => false);
}
}
const memoryV2 = getMemoryV2();
@@ -685,6 +687,20 @@ export function attachPortalSessionRoutes(
});
}
} finally {
if (releaseCandidatePaths.length > 0) {
await releaseMaterializedPageDeliveryContractsFn({
pool: authPool,
userId: uid,
relativePaths: releaseCandidatePaths,
allowPgRequired: allowPgRequiredRelease,
}).catch((error) => {
logger.warn(
`[MindSpace] delivery contract release failed for session ${sid}: ${
error instanceof Error ? error.message : error
}`,
);
});
}
endSessionPageDelivery(sid);
}
};
+95 -3
View File
@@ -112,7 +112,7 @@ function createDependencies(overrides = {}) {
return { sessionId, hooks };
},
};
return {
const setup = {
calls,
proxy,
dependencies: {
@@ -203,6 +203,30 @@ function createDependencies(overrides = {}) {
...overrides,
},
};
if (
!Object.prototype.hasOwnProperty.call(
overrides,
'releaseMaterializedPageDeliveryContractsFn',
)
) {
setup.dependencies.releaseMaterializedPageDeliveryContractsFn =
async (input) => {
const released = [];
for (const relativePath of input.relativePaths ?? []) {
if (
await setup.dependencies.markPageDeliveryContractReadyFn({
pool: input.pool,
userId: input.userId,
relativePath,
})
) {
released.push(relativePath);
}
}
return released;
};
}
return setup;
}
test('session module preserves route inventory and order', () => {
@@ -709,8 +733,8 @@ test('Finish hook preserves refresh, sync, delivery readiness, memory, and lock
'prepare-page-data',
'repair-page-data',
'prepare-contract',
'ready',
'memory',
'ready',
],
);
assert.equal(
@@ -957,5 +981,73 @@ test('Finish retries when a delivery guard is initially not ready', async () =>
assert.equal(pageDataChecks, 2);
assert.deepEqual(setup.calls.begin, ['session-1', 'session-1']);
assert.deepEqual(setup.calls.end, ['session-1', 'session-1']);
assert.deepEqual(readyPaths, ['public/survey.html']);
assert.deepEqual(readyPaths, ['public/survey.html', 'public/survey.html']);
});
test('Finish finally releases static HTML contracts when delivery guards fail', async () => {
let hooks = null;
const releasedPaths = [];
const setup = createDependencies({
finishDeliveryRetryDelaysMs: [],
getAuthPool: () => ({ id: 'pool' }),
getTkmindProxy: () => ({
async resolveTarget(sessionId) {
return `target:${sessionId}`;
},
async apiFetchTo() {
return createUpstream({
body: {
id: 'session-1',
conversation: [
{
id: 'user-1',
role: 'user',
content: 'update page',
metadata: { userVisible: true },
},
],
},
});
},
proxySessionEvents(_req, _res, _sessionId, receivedHooks) {
hooks = receivedHooks;
},
}),
getMindSpacePublicFinish: () => ({
async syncAfterFinish() {
return {
publicHtmlRelativePaths: ['public/daily-news-0813.html'],
docxSync: { missing: [] },
};
},
async preparePageDataAfterFinish() {
return {
autoBind: { bound: [], skipped: [], errors: [] },
evaluation: { structuralPageData: false, relevantFiles: [] },
};
},
}),
async maybeRepairH5HtmlAfterFinishFn() {
return { skipped: 'limit' };
},
async releaseMaterializedPageDeliveryContractsFn(input) {
releasedPaths.push(...(input.relativePaths ?? []));
return input.relativePaths ?? [];
},
});
const api = createRouterRecorder();
attachPortalSessionRoutes(api, setup.dependencies);
await api.routes.get('GET /sessions/:sessionId/events')(
createRequest(),
createResponseRecorder(),
() => {},
);
await assert.rejects(
() => hooks.onAfterFinish('session-1', 'user-1'),
/page delivery guards are not ready/,
);
assert.deepEqual(releasedPaths, ['public/daily-news-0813.html']);
assert.deepEqual(setup.calls.end, ['session-1']);
});