@ory/argus 0.13.1 → 0.13.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/build-info.json +4 -4
- package/dist/interactive-setup.js +146 -68
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repo": "ory-agent-plugins",
|
|
3
|
-
"commit": "
|
|
4
|
-
"commitShort": "
|
|
3
|
+
"commit": "4961e2084b50f7c37a1c44c93735998394afe12d",
|
|
4
|
+
"commitShort": "4961e20",
|
|
5
5
|
"branch": "main",
|
|
6
|
-
"commitDate": "2026-07-
|
|
6
|
+
"commitDate": "2026-07-14T13:13:40-07:00",
|
|
7
7
|
"dirty": false,
|
|
8
|
-
"builtAt": "2026-07-
|
|
8
|
+
"builtAt": "2026-07-14T20:17:46.098Z"
|
|
9
9
|
}
|
|
@@ -650,15 +650,20 @@ async function completeFullSetup(binName, harness, ctx) {
|
|
|
650
650
|
* The tuples reference the permission namespace (default `AgentTools`), which
|
|
651
651
|
* must be defined in the project's permission model or the write fails with
|
|
652
652
|
* `NotFound`. So we inspect the model first (see {@link inspectPermissionModel})
|
|
653
|
-
* and act on what's actually there
|
|
653
|
+
* and act on what's actually there. We provision in every case *except* when
|
|
654
|
+
* the namespace already exists:
|
|
654
655
|
*
|
|
656
|
+
* - **Namespace already defined** → grant directly, touch nothing.
|
|
655
657
|
* - **No model yet** (a freshly created Ory Network project) → offer to
|
|
656
658
|
* provision a minimal `<namespace>` model (`ory update opl`), then grant.
|
|
657
|
-
* - **
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
659
|
+
* - **Model exists (OPL source fetchable) but lacks `<namespace>`** → offer
|
|
660
|
+
* to *merge* the namespace into the existing OPL and re-upload it, leaving
|
|
661
|
+
* the other namespaces untouched, then grant. Ory Network serves the OPL
|
|
662
|
+
* source at a `location` URL in the permission config, which makes this
|
|
663
|
+
* non-destructive merge possible.
|
|
664
|
+
* - **Model provably exists but its source couldn't be read** → we can't
|
|
665
|
+
* merge and `ory update opl` overwrites the whole OPL, so we refuse to
|
|
666
|
+
* clobber it: print the exact snippet to add and skip the grant.
|
|
662
667
|
*
|
|
663
668
|
* Best-effort throughout: any failure prints a short, actionable note rather
|
|
664
669
|
* than a wall of errors. Observe mode (the install default) keeps tools working
|
|
@@ -670,10 +675,36 @@ async function bootstrapPermissionsViaOry(runner, projectId, harness, subject, p
|
|
|
670
675
|
if (tools.length === 0)
|
|
671
676
|
return;
|
|
672
677
|
// Ensure the namespace the tuples reference exists, or the write NotFounds.
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
678
|
+
// We only skip provisioning when the namespace is already defined; every
|
|
679
|
+
// other state provisions. When we can fetch the existing OPL source we merge
|
|
680
|
+
// our namespace into it (non-destructive); otherwise we create a fresh
|
|
681
|
+
// minimal model. The one case we refuse is a model that provably exists but
|
|
682
|
+
// whose source we couldn't read — overwriting it would clobber it.
|
|
683
|
+
const model = await inspectPermissionModel(runner, projectId, namespace);
|
|
684
|
+
if (model.kind === "merge") {
|
|
685
|
+
// A model exists and we have its OPL source — append our namespace without
|
|
686
|
+
// disturbing the others (prompted).
|
|
687
|
+
ui.heading(`This project has a permission model, but it doesn't define '${namespace}'.`);
|
|
688
|
+
ui.hint("It can be added to the existing model without changing the other namespaces.");
|
|
689
|
+
ui.blank();
|
|
690
|
+
const answer = await prompt(ui.promptLine(`Add '${namespace}' to this project's permission model now?`, {
|
|
691
|
+
hint: "[Y/n]",
|
|
692
|
+
}));
|
|
693
|
+
if (isNo(answer)) {
|
|
694
|
+
printPermissionModelHelp(namespace);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (!provisionPermissionModel(runner, projectId, namespace, model.opl)) {
|
|
698
|
+
ui.warning(`Could not add '${namespace}' to the permission model automatically.`);
|
|
699
|
+
printPermissionModelHelp(namespace);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
ui.success(`Added '${namespace}' to the permission model.`);
|
|
703
|
+
}
|
|
704
|
+
else if (model.kind === "empty" || model.kind === "unknown") {
|
|
705
|
+
// No model (or one we couldn't read at all) — provision a minimal one
|
|
706
|
+
// (prompted). Reading the config failing is treated as "nothing there".
|
|
707
|
+
ui.heading(`This project has no '${namespace}' permission model yet —`);
|
|
677
708
|
ui.hint("without it, permission checks can't be granted or enforced.");
|
|
678
709
|
ui.blank();
|
|
679
710
|
const answer = await prompt(ui.promptLine(`Create a minimal '${namespace}' permission model on this project now?`, { hint: "[Y/n]" }));
|
|
@@ -688,16 +719,15 @@ async function bootstrapPermissionsViaOry(runner, projectId, harness, subject, p
|
|
|
688
719
|
}
|
|
689
720
|
ui.success(`Created the '${namespace}' permission model.`);
|
|
690
721
|
}
|
|
691
|
-
else if (
|
|
692
|
-
// A model exists but
|
|
693
|
-
//
|
|
694
|
-
ui.heading(`This project has a permission model, but it doesn't define '${namespace}'
|
|
695
|
-
ui.hint("
|
|
722
|
+
else if (model.kind === "blocked") {
|
|
723
|
+
// A model provably exists but we couldn't fetch its source, so we can't
|
|
724
|
+
// merge and `ory update opl` would overwrite it. Guide instead of clobber.
|
|
725
|
+
ui.heading(`This project has a permission model, but it doesn't define '${namespace}'`);
|
|
726
|
+
ui.hint("and its source couldn't be read, so adding it automatically might overwrite it.");
|
|
696
727
|
printPermissionModelHelp(namespace);
|
|
697
728
|
return;
|
|
698
729
|
}
|
|
699
|
-
// "present" (namespace already defined)
|
|
700
|
-
// model) fall through and attempt the grant best-effort.
|
|
730
|
+
// "present" (namespace already defined) falls through and grants directly.
|
|
701
731
|
const subjectPatch = "subjectSet" in subject
|
|
702
732
|
? {
|
|
703
733
|
subject_set: {
|
|
@@ -727,26 +757,13 @@ async function bootstrapPermissionsViaOry(runner, projectId, harness, subject, p
|
|
|
727
757
|
printPermissionModelHelp(namespace);
|
|
728
758
|
}
|
|
729
759
|
/**
|
|
730
|
-
* Inspect the project's permission model
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
* Network project reports this as an empty list, a `null`, or
|
|
736
|
-
* an absent `namespaces` key — all of which mean "nothing
|
|
737
|
-
* defined yet", so provisioning a minimal model is safe.
|
|
738
|
-
* - `missing` — a model exists and defines *other* namespaces but not this
|
|
739
|
-
* one. `ory update opl` overwrites the whole OPL, so we must
|
|
740
|
-
* not auto-provision (it would clobber the existing model).
|
|
741
|
-
* - `unknown` — the config couldn't be read/parsed; make no assumptions and
|
|
742
|
-
* attempt the grant best-effort.
|
|
743
|
-
*
|
|
744
|
-
* The earlier version only recognized the exact `namespaces: []` shape as
|
|
745
|
-
* "empty", so a fresh project reporting `null`/absent namespaces (or one that
|
|
746
|
-
* already had unrelated namespaces) silently skipped provisioning and the grant
|
|
747
|
-
* then failed with `NotFound`.
|
|
760
|
+
* Inspect the project's permission model. Ory Network reports the config in one
|
|
761
|
+
* of two shapes: `{ namespaces: [] }` (or `null`) for a project with no model,
|
|
762
|
+
* and `{ namespaces: { location: "https://….txt" } }` for an OPL-configured
|
|
763
|
+
* one — where the `.txt` is the full OPL *source*. We fetch that source so a
|
|
764
|
+
* missing namespace can be merged in rather than clobbering the whole model.
|
|
748
765
|
*/
|
|
749
|
-
function inspectPermissionModel(runner, projectId, namespace) {
|
|
766
|
+
async function inspectPermissionModel(runner, projectId, namespace) {
|
|
750
767
|
const r = runner.exec([
|
|
751
768
|
"get",
|
|
752
769
|
"permission-config",
|
|
@@ -756,47 +773,75 @@ function inspectPermissionModel(runner, projectId, namespace) {
|
|
|
756
773
|
"json",
|
|
757
774
|
]);
|
|
758
775
|
if (r.status !== 0)
|
|
759
|
-
return "unknown";
|
|
776
|
+
return { kind: "unknown" };
|
|
760
777
|
const cfg = safeJsonParse(r.stdout);
|
|
761
778
|
if (!cfg || typeof cfg !== "object")
|
|
762
|
-
return "unknown";
|
|
779
|
+
return { kind: "unknown" };
|
|
763
780
|
const ns = cfg.namespaces;
|
|
764
|
-
// A fresh project reports no model as null / an absent key
|
|
781
|
+
// A fresh project reports no model as null / an absent key.
|
|
765
782
|
if (ns == null)
|
|
766
|
-
return "empty";
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
if (ns
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
783
|
+
return { kind: "empty" };
|
|
784
|
+
// OPL-configured form: { namespaces: { location: "https://….txt" } }.
|
|
785
|
+
// The .txt holds the full OPL source, so we can merge into it.
|
|
786
|
+
if (!Array.isArray(ns) && typeof ns === "object") {
|
|
787
|
+
const location = ns.location;
|
|
788
|
+
if (typeof location !== "string" || !location)
|
|
789
|
+
return { kind: "empty" };
|
|
790
|
+
const opl = await fetchOplSource(location);
|
|
791
|
+
// A model exists but its source is unreadable — can't merge, mustn't clobber.
|
|
792
|
+
if (opl == null)
|
|
793
|
+
return { kind: "blocked" };
|
|
794
|
+
return oplDefinesClass(opl, namespace)
|
|
795
|
+
? { kind: "present" }
|
|
796
|
+
: { kind: "merge", opl };
|
|
797
|
+
}
|
|
798
|
+
// Inline compiled form: { namespaces: [{ name, id }, …] } (legacy / self-hosted).
|
|
799
|
+
// We only have names, not source, so a missing namespace can't be merged.
|
|
800
|
+
if (Array.isArray(ns)) {
|
|
801
|
+
if (ns.length === 0)
|
|
802
|
+
return { kind: "empty" };
|
|
803
|
+
const names = ns
|
|
804
|
+
.map((n) => n && typeof n === "object"
|
|
805
|
+
? n.name
|
|
806
|
+
: undefined)
|
|
807
|
+
.filter((n) => typeof n === "string");
|
|
808
|
+
return names.includes(namespace) ? { kind: "present" } : { kind: "blocked" };
|
|
809
|
+
}
|
|
810
|
+
return { kind: "unknown" };
|
|
811
|
+
}
|
|
812
|
+
/** Fetch the OPL source served at a permission-config `location` URL. */
|
|
813
|
+
async function fetchOplSource(url) {
|
|
814
|
+
try {
|
|
815
|
+
const res = await fetch(url);
|
|
816
|
+
if (!res.ok)
|
|
817
|
+
return null;
|
|
818
|
+
return await res.text();
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
/** Whether an OPL source defines `class <name>`. */
|
|
825
|
+
function oplDefinesClass(opl, name) {
|
|
826
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
827
|
+
return new RegExp(`\\bclass\\s+${escaped}\\b`).test(opl);
|
|
777
828
|
}
|
|
778
829
|
/**
|
|
779
|
-
* Provision a
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
830
|
+
* Provision a permission model that defines `<namespace>` with a `use`
|
|
831
|
+
* relation, via `ory update opl`. The namespace becomes an OPL class, so it
|
|
832
|
+
* must be a valid identifier; if not, we bail (caller guides instead).
|
|
833
|
+
*
|
|
834
|
+
* When `existingOpl` is given, the namespace is appended to that source
|
|
835
|
+
* (preserving the model's other namespaces); otherwise a fresh minimal model
|
|
836
|
+
* is generated. Writes the OPL to a temp file because `ory update opl` reads
|
|
837
|
+
* `--file` only (no stdin). Returns true on success.
|
|
784
838
|
*/
|
|
785
|
-
function provisionPermissionModel(runner, projectId, namespace) {
|
|
839
|
+
function provisionPermissionModel(runner, projectId, namespace, existingOpl) {
|
|
786
840
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(namespace))
|
|
787
841
|
return false;
|
|
788
|
-
const opl =
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
"class User implements Namespace {}",
|
|
792
|
-
"",
|
|
793
|
-
`class ${namespace} implements Namespace {`,
|
|
794
|
-
" related: {",
|
|
795
|
-
" use: User[]",
|
|
796
|
-
" }",
|
|
797
|
-
"}",
|
|
798
|
-
"",
|
|
799
|
-
].join("\n");
|
|
842
|
+
const opl = existingOpl !== undefined
|
|
843
|
+
? mergeNamespaceIntoOpl(existingOpl, namespace)
|
|
844
|
+
: freshOpl(namespace);
|
|
800
845
|
let dir;
|
|
801
846
|
try {
|
|
802
847
|
dir = fs.mkdtempSync(path.join(os.tmpdir(), "ory-opl-"));
|
|
@@ -832,6 +877,39 @@ function provisionPermissionModel(runner, projectId, namespace) {
|
|
|
832
877
|
}
|
|
833
878
|
}
|
|
834
879
|
}
|
|
880
|
+
/** A minimal standalone OPL defining `User` and `<namespace>` with `use`. */
|
|
881
|
+
function freshOpl(namespace) {
|
|
882
|
+
return [
|
|
883
|
+
'import { Namespace } from "@ory/permission-namespace-types"',
|
|
884
|
+
"",
|
|
885
|
+
"class User implements Namespace {}",
|
|
886
|
+
"",
|
|
887
|
+
`class ${namespace} implements Namespace {`,
|
|
888
|
+
" related: {",
|
|
889
|
+
" use: User[]",
|
|
890
|
+
" }",
|
|
891
|
+
"}",
|
|
892
|
+
"",
|
|
893
|
+
].join("\n");
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Append a `<namespace>` class (with a `use: User[]` relation) to an existing
|
|
897
|
+
* OPL source, leaving every other namespace untouched. Adds a `User` class too
|
|
898
|
+
* when the source doesn't already define one, since `use` is typed `User[]`.
|
|
899
|
+
*/
|
|
900
|
+
function mergeNamespaceIntoOpl(existingOpl, namespace) {
|
|
901
|
+
let opl = existingOpl.replace(/\s*$/, "\n");
|
|
902
|
+
if (!oplDefinesClass(opl, "User")) {
|
|
903
|
+
opl += "\nclass User implements Namespace {}\n";
|
|
904
|
+
}
|
|
905
|
+
opl +=
|
|
906
|
+
`\nclass ${namespace} implements Namespace {\n` +
|
|
907
|
+
" related: {\n" +
|
|
908
|
+
" use: User[]\n" +
|
|
909
|
+
" }\n" +
|
|
910
|
+
"}\n";
|
|
911
|
+
return opl;
|
|
912
|
+
}
|
|
835
913
|
function printPermissionModelHelp(namespace) {
|
|
836
914
|
// When the namespace is a valid OPL class name, show the exact snippet to
|
|
837
915
|
// paste into the model — that's the concrete "how" behind "define it". The
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/argus",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "Ory Argus: the core API for building authentication, authorization, and audit into AI agent harness plugins, extensions, and custom integrations",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ory.com",
|