| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import assert from 'node:assert/strict'; |
| import { describe, test } from 'node:test'; |
| import { |
| ARTIFACT_ENTITY_ID_MAX_CHARS, |
| ARTIFACT_TURN_KEY_MAX_CHARS, |
| ARTIFACT_SOURCES, |
| canUserDeleteArtifact, |
| isArtifactChildResultOutput, |
| isArtifactSharedSessionReadable, |
| isArtifactUserVisible, |
| isArtifactTurnKey, |
| isCanonicalArtifactEntityId, |
| } from '../artifacts.js'; |
| |
| describe('canonical Artifact entity identity', () => { |
| test('accepts the shared ASCII grammar through the 128-character boundary', () => { |
| assert.equal(ARTIFACT_ENTITY_ID_MAX_CHARS, 128); |
| assert.equal(isCanonicalArtifactEntityId('Artifact_01-session'), true); |
| assert.equal(isCanonicalArtifactEntityId('a'.repeat(ARTIFACT_ENTITY_ID_MAX_CHARS)), true); |
| }); |
| |
| test('rejects values outside the canonical grammar', () => { |
| for (const value of ['', 'a'.repeat(ARTIFACT_ENTITY_ID_MAX_CHARS + 1), 'artifact/id', null]) { |
| assert.equal(isCanonicalArtifactEntityId(value), false, JSON.stringify(value)); |
| } |
| }); |
| }); |
| |
| describe('Artifact turn key', () => { |
| test('accepts bounded opaque turn keys without coupling to synthetic namespaces', () => { |
| assert.equal(isArtifactTurnKey('未来:sequence/分支'), true); |
| assert.equal(isArtifactTurnKey('x'.repeat(ARTIFACT_TURN_KEY_MAX_CHARS)), true); |
| }); |
| |
| test('rejects empty, unbounded, and control-bearing turn keys', () => { |
| for (const value of ['', 'turn\n1', 'x'.repeat(ARTIFACT_TURN_KEY_MAX_CHARS + 1), null]) { |
| assert.equal(isArtifactTurnKey(value), false, JSON.stringify(value)); |
| } |
| }); |
| }); |
| |
| describe('Artifact source policy', () => { |
| test('keeps workflow-owned evidence out of independent user deletion', () => { |
| for (const source of ARTIFACT_SOURCES) { |
| assert.equal( |
| canUserDeleteArtifact({ source }), |
| source === 'tool_result' || source === 'user_upload', |
| source, |
| ); |
| } |
| }); |
| test('includes produced outputs in child results without leaking internal artifacts', () => { |
| assert.equal(isArtifactChildResultOutput({ source: 'tool_result' }), true); |
| assert.equal(isArtifactChildResultOutput({ source: 'subagent_writeback' }), true); |
| assert.equal(isArtifactChildResultOutput({ source: 'tool_result_archive' }), false); |
| assert.equal(isArtifactChildResultOutput({ source: 'user_upload' }), false); |
| }); |
| |
| test('keeps projection artifacts internal, durable, and readable in shared sessions', () => { |
| const projection = { source: 'tool_result_projection' as const }; |
| |
| assert.equal(isArtifactUserVisible(projection), false); |
| assert.equal(isArtifactSharedSessionReadable(projection), true); |
| }); |
| |
| test('exposes directly written HTML files while keeping other tool results internal', () => { |
| assert.equal(isArtifactUserVisible({ source: 'tool_result', kind: 'html' }), true); |
| assert.equal(isArtifactUserVisible({ source: 'tool_result', kind: 'file' }), false); |
| assert.equal(isArtifactUserVisible({ source: 'tool_result', kind: 'diff' }), false); |
| }); |
| }); |