@tinycloud/cli 1.3.0-beta.19 → 1.3.0-beta.21
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/index.js +177 -14
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +154 -13
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +7 -7
- package/skills/tc-cli/REFERENCE.md +3 -1
package/dist/index.js
CHANGED
|
@@ -12822,6 +12822,20 @@ var v3TargetSchema = external_exports.object({
|
|
|
12822
12822
|
nodeAudience: external_exports.string().min(1),
|
|
12823
12823
|
spaceId: external_exports.string().refine(isCanonicalPathSegment, { message: "expected a canonical space id" })
|
|
12824
12824
|
}).strict();
|
|
12825
|
+
var ARTIFACT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{2,62}$/;
|
|
12826
|
+
var artifactPinSchema = external_exports.object({
|
|
12827
|
+
v: external_exports.literal(1),
|
|
12828
|
+
artifactId: external_exports.string().regex(ARTIFACT_ID_PATTERN),
|
|
12829
|
+
version: external_exports.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
12830
|
+
/** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
|
|
12831
|
+
manifestSha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
12832
|
+
/** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
|
|
12833
|
+
files: external_exports.array(external_exports.object({
|
|
12834
|
+
path: external_exports.string().min(1).max(255),
|
|
12835
|
+
sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
12836
|
+
bytes: external_exports.number().int().nonnegative()
|
|
12837
|
+
}).strict()).length(1)
|
|
12838
|
+
}).strict();
|
|
12825
12839
|
var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
|
|
12826
12840
|
version: external_exports.literal(3),
|
|
12827
12841
|
shareId: external_exports.string().min(1),
|
|
@@ -12841,7 +12855,13 @@ var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
|
|
|
12841
12855
|
expiry: external_exports.string().datetime({ offset: true }),
|
|
12842
12856
|
display: displaySchema,
|
|
12843
12857
|
encrypted: external_exports.literal(true),
|
|
12844
|
-
metadata: contentMetadataSchema
|
|
12858
|
+
metadata: contentMetadataSchema,
|
|
12859
|
+
/**
|
|
12860
|
+
* TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
|
|
12861
|
+
* every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
|
|
12862
|
+
* The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
|
|
12863
|
+
*/
|
|
12864
|
+
artifactPin: artifactPinSchema.optional()
|
|
12845
12865
|
}).strict();
|
|
12846
12866
|
function validateV3Invariants(value, ctx) {
|
|
12847
12867
|
const actions = [...value.actions];
|
|
@@ -12939,6 +12959,12 @@ function canonicalize(value) {
|
|
|
12939
12959
|
return serialize(value);
|
|
12940
12960
|
}
|
|
12941
12961
|
var MAX_INLINE_BYTES = 256 * 1024;
|
|
12962
|
+
var ARTIFACT_ROOT = "artifacts";
|
|
12963
|
+
function parseArtifactVersionPath(path) {
|
|
12964
|
+
const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
|
|
12965
|
+
if (match === null) return void 0;
|
|
12966
|
+
return { artifactId: match[1], version: Number(match[2]), file: match[3] };
|
|
12967
|
+
}
|
|
12942
12968
|
var UNSAFE_FILENAME_CODE_POINT = /[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
|
|
12943
12969
|
function hasUnsafeFilenameCodePoint(value) {
|
|
12944
12970
|
return UNSAFE_FILENAME_CODE_POINT.test(value);
|
|
@@ -12950,7 +12976,9 @@ function canonicalShareFilename(value) {
|
|
|
12950
12976
|
}
|
|
12951
12977
|
return canonical;
|
|
12952
12978
|
}
|
|
12953
|
-
|
|
12979
|
+
function sha256Hex(bytes) {
|
|
12980
|
+
return Array.from(sha2562(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
12981
|
+
}
|
|
12954
12982
|
var ARTIFACT_MANIFEST_NAME = "manifest.json";
|
|
12955
12983
|
var ARTIFACT_MANIFEST_VERSION = 1;
|
|
12956
12984
|
var ARTIFACT_MAX_PUBLISH_ATTEMPTS = 16;
|
|
@@ -12964,9 +12992,6 @@ var ArtifactError = class extends Error {
|
|
|
12964
12992
|
this.code = code22;
|
|
12965
12993
|
}
|
|
12966
12994
|
};
|
|
12967
|
-
function sha256Hex(bytes) {
|
|
12968
|
-
return Array.from(sha2562(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
12969
|
-
}
|
|
12970
12995
|
function newArtifactId(random = (length23) => crypto.getRandomValues(new Uint8Array(length23))) {
|
|
12971
12996
|
return `art_${Array.from(random(8), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
12972
12997
|
}
|
|
@@ -12989,11 +13014,6 @@ function artifactVersionResource(artifactId, version2) {
|
|
|
12989
13014
|
function artifactVersionFilePath(artifactId, version2, filename) {
|
|
12990
13015
|
return `${artifactVersionResource(artifactId, version2)}/${filename}`;
|
|
12991
13016
|
}
|
|
12992
|
-
function parseArtifactVersionPath(path) {
|
|
12993
|
-
const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
|
|
12994
|
-
if (match === null) return void 0;
|
|
12995
|
-
return { artifactId: match[1], version: Number(match[2]), file: match[3] };
|
|
12996
|
-
}
|
|
12997
13017
|
function manifestBytes(manifest) {
|
|
12998
13018
|
return new TextEncoder().encode(canonicalize(manifest));
|
|
12999
13019
|
}
|
|
@@ -13139,6 +13159,17 @@ async function publishArtifactVersion(store, input) {
|
|
|
13139
13159
|
}
|
|
13140
13160
|
throw new ArtifactError("conflict", "could not claim an artifact version number after repeated races; retry");
|
|
13141
13161
|
}
|
|
13162
|
+
function buildArtifactPin(version2, filename) {
|
|
13163
|
+
const entry = version2.manifest.files.find((candidate) => candidate.path === filename);
|
|
13164
|
+
if (entry === void 0) throw new ArtifactError("invalid-argument", "artifact manifest does not list that file");
|
|
13165
|
+
return {
|
|
13166
|
+
v: 1,
|
|
13167
|
+
artifactId: version2.artifactId,
|
|
13168
|
+
version: version2.version,
|
|
13169
|
+
manifestSha256: version2.manifestSha256,
|
|
13170
|
+
files: [{ path: entry.path, sha256: entry.sha256, bytes: entry.bytes }]
|
|
13171
|
+
};
|
|
13172
|
+
}
|
|
13142
13173
|
|
|
13143
13174
|
// ../share-sdk/dist/index.js
|
|
13144
13175
|
init_sha2();
|
|
@@ -18442,6 +18473,20 @@ var v3TargetSchema2 = external_exports2.object({
|
|
|
18442
18473
|
nodeAudience: external_exports2.string().min(1),
|
|
18443
18474
|
spaceId: external_exports2.string().refine(isCanonicalPathSegment2, { message: "expected a canonical space id" })
|
|
18444
18475
|
}).strict();
|
|
18476
|
+
var ARTIFACT_ID_PATTERN2 = /^[a-z0-9][a-z0-9_-]{2,62}$/;
|
|
18477
|
+
var artifactPinSchema2 = external_exports2.object({
|
|
18478
|
+
v: external_exports2.literal(1),
|
|
18479
|
+
artifactId: external_exports2.string().regex(ARTIFACT_ID_PATTERN2),
|
|
18480
|
+
version: external_exports2.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
18481
|
+
/** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
|
|
18482
|
+
manifestSha256: external_exports2.string().regex(/^[0-9a-f]{64}$/),
|
|
18483
|
+
/** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
|
|
18484
|
+
files: external_exports2.array(external_exports2.object({
|
|
18485
|
+
path: external_exports2.string().min(1).max(255),
|
|
18486
|
+
sha256: external_exports2.string().regex(/^[0-9a-f]{64}$/),
|
|
18487
|
+
bytes: external_exports2.number().int().nonnegative()
|
|
18488
|
+
}).strict()).length(1)
|
|
18489
|
+
}).strict();
|
|
18445
18490
|
var unsignedShareEnvelopeV3BaseSchema2 = external_exports2.object({
|
|
18446
18491
|
version: external_exports2.literal(3),
|
|
18447
18492
|
shareId: external_exports2.string().min(1),
|
|
@@ -18461,7 +18506,13 @@ var unsignedShareEnvelopeV3BaseSchema2 = external_exports2.object({
|
|
|
18461
18506
|
expiry: external_exports2.string().datetime({ offset: true }),
|
|
18462
18507
|
display: displaySchema2,
|
|
18463
18508
|
encrypted: external_exports2.literal(true),
|
|
18464
|
-
metadata: contentMetadataSchema2
|
|
18509
|
+
metadata: contentMetadataSchema2,
|
|
18510
|
+
/**
|
|
18511
|
+
* TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
|
|
18512
|
+
* every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
|
|
18513
|
+
* The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
|
|
18514
|
+
*/
|
|
18515
|
+
artifactPin: artifactPinSchema2.optional()
|
|
18465
18516
|
}).strict();
|
|
18466
18517
|
function validateV3Invariants2(value, ctx) {
|
|
18467
18518
|
const actions = [...value.actions];
|
|
@@ -18843,6 +18894,80 @@ async function parseSealedInlineShareUrl(url, options = {}) {
|
|
|
18843
18894
|
if (await computeCid(ciphertext) !== record.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
|
|
18844
18895
|
return { kind: "inline", ciphertextCid: record.cid, ciphertext, key32 };
|
|
18845
18896
|
}
|
|
18897
|
+
var ARTIFACT_ROOT2 = "artifacts";
|
|
18898
|
+
function parseArtifactVersionPath2(path) {
|
|
18899
|
+
const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
|
|
18900
|
+
if (match === null) return void 0;
|
|
18901
|
+
return { artifactId: match[1], version: Number(match[2]), file: match[3] };
|
|
18902
|
+
}
|
|
18903
|
+
var ArtifactPinError = class extends Error {
|
|
18904
|
+
code;
|
|
18905
|
+
/** The code is the message: stable, short, and what callers and tests match on. */
|
|
18906
|
+
constructor(code32) {
|
|
18907
|
+
super(code32);
|
|
18908
|
+
this.name = "ArtifactPinError";
|
|
18909
|
+
this.code = code32;
|
|
18910
|
+
}
|
|
18911
|
+
};
|
|
18912
|
+
function artifactBindingView(envelope) {
|
|
18913
|
+
return {
|
|
18914
|
+
...envelope.artifactPin === void 0 ? {} : { artifactPin: envelope.artifactPin },
|
|
18915
|
+
target: envelope.target,
|
|
18916
|
+
contentSource: envelope.contentSource,
|
|
18917
|
+
resource: envelope.resource,
|
|
18918
|
+
actions: envelope.actions,
|
|
18919
|
+
policyKvActions: envelope.policy.capabilityCeiling.flatMap((capability) => capability.kind === "kv" ? capability.actions : [])
|
|
18920
|
+
};
|
|
18921
|
+
}
|
|
18922
|
+
function sourcePath(view) {
|
|
18923
|
+
const resource = view.contentSource.kvResource;
|
|
18924
|
+
const marker = "/kv/";
|
|
18925
|
+
const at = resource.indexOf(marker);
|
|
18926
|
+
if (at <= 0 || resource.slice(0, at) !== view.target.spaceId) {
|
|
18927
|
+
throw new ArtifactPinError("pin-source-mismatch");
|
|
18928
|
+
}
|
|
18929
|
+
return resource.slice(at + marker.length);
|
|
18930
|
+
}
|
|
18931
|
+
function assertArtifactBinding(view, expected = {}) {
|
|
18932
|
+
const pin = view.artifactPin;
|
|
18933
|
+
const path = sourcePath(view);
|
|
18934
|
+
const inArtifacts = path === ARTIFACT_ROOT2 || path.startsWith(`${ARTIFACT_ROOT2}/`);
|
|
18935
|
+
if (!inArtifacts) {
|
|
18936
|
+
if (pin !== void 0) throw new ArtifactPinError("pin-source-mismatch");
|
|
18937
|
+
return void 0;
|
|
18938
|
+
}
|
|
18939
|
+
if (pin === void 0) throw new ArtifactPinError("missing-pin");
|
|
18940
|
+
const parsed = parseArtifactVersionPath2(path);
|
|
18941
|
+
if (parsed === void 0) throw new ArtifactPinError("unsupported-artifact-path");
|
|
18942
|
+
const file = pin.files[0];
|
|
18943
|
+
const source = view.contentSource;
|
|
18944
|
+
if (
|
|
18945
|
+
// The stored ciphertext of one file: bounded like content (100 MiB) plus the envelope's own framing.
|
|
18946
|
+
file.bytes > 128 * 1024 * 1024 || pin.artifactId !== parsed.artifactId || pin.version !== parsed.version || file.path !== parsed.file || view.resource.kind !== "exact" || view.resource.path !== path || source.selector !== "exact" || source.mode !== "immutable" || source.initialCiphertextDigestHex !== file.sha256
|
|
18947
|
+
) {
|
|
18948
|
+
throw new ArtifactPinError("pin-source-mismatch");
|
|
18949
|
+
}
|
|
18950
|
+
if (view.actions.includes("edit") || view.policyKvActions.includes("tinycloud.kv/put")) {
|
|
18951
|
+
throw new ArtifactPinError("pinned-share-writable");
|
|
18952
|
+
}
|
|
18953
|
+
if (expected.artifactId !== void 0 && expected.artifactId !== pin.artifactId || expected.version !== void 0 && expected.version !== pin.version) {
|
|
18954
|
+
throw new ArtifactPinError("expectation-mismatch");
|
|
18955
|
+
}
|
|
18956
|
+
return pin;
|
|
18957
|
+
}
|
|
18958
|
+
function verifyArtifactShare(envelope, options) {
|
|
18959
|
+
if (envelope.signature.signerDid !== options.expectedOwnerDid) {
|
|
18960
|
+
throw new ArtifactPinError("wrong-signer");
|
|
18961
|
+
}
|
|
18962
|
+
let valid;
|
|
18963
|
+
try {
|
|
18964
|
+
valid = verifyEnvelopeV3SignatureOnly(envelope) && envelope.policy.ownerDid === options.expectedOwnerDid;
|
|
18965
|
+
} catch {
|
|
18966
|
+
valid = false;
|
|
18967
|
+
}
|
|
18968
|
+
if (!valid) throw new ArtifactPinError("bad-signature");
|
|
18969
|
+
return assertArtifactBinding(artifactBindingView(envelope), options.expected);
|
|
18970
|
+
}
|
|
18846
18971
|
var SHARE_RESULT_VERSION = 1;
|
|
18847
18972
|
var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
|
|
18848
18973
|
var ShareReceiveError = class extends Error {
|
|
@@ -18873,7 +18998,8 @@ function metadataFor(envelope, origin) {
|
|
|
18873
18998
|
...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
|
|
18874
18999
|
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
18875
19000
|
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
18876
|
-
}
|
|
19001
|
+
},
|
|
19002
|
+
...envelope.artifactPin === void 0 ? {} : { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } }
|
|
18877
19003
|
};
|
|
18878
19004
|
}
|
|
18879
19005
|
async function resolvePolicyShare(link3, options) {
|
|
@@ -18904,6 +19030,12 @@ async function resolvePolicyShare(link3, options) {
|
|
|
18904
19030
|
} catch {
|
|
18905
19031
|
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
18906
19032
|
}
|
|
19033
|
+
try {
|
|
19034
|
+
verifyArtifactShare(envelope, { expectedOwnerDid: envelope.policy.ownerDid });
|
|
19035
|
+
} catch (error) {
|
|
19036
|
+
if (!(error instanceof ArtifactPinError)) throw error;
|
|
19037
|
+
throw new ShareReceiveError("envelope-invalid", `share artifact pin is invalid (${error.code})`);
|
|
19038
|
+
}
|
|
18907
19039
|
options.signal?.throwIfAborted();
|
|
18908
19040
|
const expiry = Date.parse(envelope.expiry);
|
|
18909
19041
|
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
@@ -19568,6 +19700,14 @@ function assertMailboxCommitment(commitment, target) {
|
|
|
19568
19700
|
async function publishAddressedShare(input) {
|
|
19569
19701
|
assertSafeInput(input);
|
|
19570
19702
|
const { target } = prepareAddressedShare(input);
|
|
19703
|
+
assertArtifactBinding({
|
|
19704
|
+
...input.artifactPin === void 0 ? {} : { artifactPin: input.artifactPin },
|
|
19705
|
+
target: { spaceId: input.spaceId },
|
|
19706
|
+
contentSource: input.contentSource,
|
|
19707
|
+
resource: input.resource,
|
|
19708
|
+
actions: input.actions,
|
|
19709
|
+
policyKvActions: input.policyActions
|
|
19710
|
+
});
|
|
19571
19711
|
const options = { ...input, filename: canonicalShareFilename2(input.filename) };
|
|
19572
19712
|
if (target.kind !== "recipientDid") assertMailboxCommitment(options.credentialRequirement, target);
|
|
19573
19713
|
const expiry = rfc3339Seconds(options.expiresAt);
|
|
@@ -19634,7 +19774,8 @@ async function publishAddressedShare(input) {
|
|
|
19634
19774
|
filename: options.filename,
|
|
19635
19775
|
...options.mediaType.startsWith("text/") ? { encoding: "utf-8" } : {},
|
|
19636
19776
|
...options.artifact === void 0 ? {} : { artifact: options.artifact }
|
|
19637
|
-
}
|
|
19777
|
+
},
|
|
19778
|
+
...options.artifactPin === void 0 ? {} : { artifactPin: options.artifactPin }
|
|
19638
19779
|
};
|
|
19639
19780
|
unsignedShareEnvelopeV3Schema2.parse(unsigned);
|
|
19640
19781
|
const envelopeSignature = await options.authority.sign(sha2562(textEncoder.encode(`${ENVELOPE_V3_DOMAIN}${canonicalize2(unsigned)}`)));
|
|
@@ -25805,6 +25946,20 @@ var v3TargetSchema3 = external_exports3.object({
|
|
|
25805
25946
|
nodeAudience: external_exports3.string().min(1),
|
|
25806
25947
|
spaceId: external_exports3.string().refine(isCanonicalPathSegment3, { message: "expected a canonical space id" })
|
|
25807
25948
|
}).strict();
|
|
25949
|
+
var ARTIFACT_ID_PATTERN3 = /^[a-z0-9][a-z0-9_-]{2,62}$/;
|
|
25950
|
+
var artifactPinSchema3 = external_exports3.object({
|
|
25951
|
+
v: external_exports3.literal(1),
|
|
25952
|
+
artifactId: external_exports3.string().regex(ARTIFACT_ID_PATTERN3),
|
|
25953
|
+
version: external_exports3.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
25954
|
+
/** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
|
|
25955
|
+
manifestSha256: external_exports3.string().regex(/^[0-9a-f]{64}$/),
|
|
25956
|
+
/** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
|
|
25957
|
+
files: external_exports3.array(external_exports3.object({
|
|
25958
|
+
path: external_exports3.string().min(1).max(255),
|
|
25959
|
+
sha256: external_exports3.string().regex(/^[0-9a-f]{64}$/),
|
|
25960
|
+
bytes: external_exports3.number().int().nonnegative()
|
|
25961
|
+
}).strict()).length(1)
|
|
25962
|
+
}).strict();
|
|
25808
25963
|
var unsignedShareEnvelopeV3BaseSchema3 = external_exports3.object({
|
|
25809
25964
|
version: external_exports3.literal(3),
|
|
25810
25965
|
shareId: external_exports3.string().min(1),
|
|
@@ -25824,7 +25979,13 @@ var unsignedShareEnvelopeV3BaseSchema3 = external_exports3.object({
|
|
|
25824
25979
|
expiry: external_exports3.string().datetime({ offset: true }),
|
|
25825
25980
|
display: displaySchema3,
|
|
25826
25981
|
encrypted: external_exports3.literal(true),
|
|
25827
|
-
metadata: contentMetadataSchema3
|
|
25982
|
+
metadata: contentMetadataSchema3,
|
|
25983
|
+
/**
|
|
25984
|
+
* TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
|
|
25985
|
+
* every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
|
|
25986
|
+
* The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
|
|
25987
|
+
*/
|
|
25988
|
+
artifactPin: artifactPinSchema3.optional()
|
|
25828
25989
|
}).strict();
|
|
25829
25990
|
function validateV3Invariants3(value, ctx) {
|
|
25830
25991
|
const actions = [...value.actions];
|
|
@@ -27256,6 +27417,8 @@ function createShareAuthorityAdapters(input = {}) {
|
|
|
27256
27417
|
actions,
|
|
27257
27418
|
policyActions,
|
|
27258
27419
|
contentSource,
|
|
27420
|
+
// The owner-signed pin: artifact id, version, manifest digest and the digest of the stored ciphertext this share reads.
|
|
27421
|
+
...artifactVersion === void 0 ? {} : { artifactPin: buildArtifactPin(artifactVersion, storageName) },
|
|
27259
27422
|
...prepared.credentialRequirement === void 0 ? {} : { credentialRequirement: prepared.credentialRequirement },
|
|
27260
27423
|
// tinycloud-node 1.17.2 signs a delivery receipt only for the envelope's
|
|
27261
27424
|
// own signed delivery address (1.17.3 accepts it and no longer requires
|