From 40920f9775ea5b0bd111623289d002ecc3965ec9 Mon Sep 17 00:00:00 2001 From: PrivateGER Date: Fri, 3 Jul 2026 12:25:59 +0200 Subject: [PATCH 1/2] feat(frontend): add Lua syntax reference to MRF policy admin UI Adds a collapsible 'Syntax reference' panel documenting the full policy API surface: the filter(ctx) contract, params schema types, ctx fields, mrf.accept/reject/rewrite decisions, mrf.activity.*/mrf.note.*/mrf.lookup.* helpers, sandbox constraints, and a worked example. Content is grounded in the SANDBOX_PRELUDE and parseDecision rules. Also renders string_array params as comma-separated inputs (matching the existing coerceParams round-trip) and reseeds the params form from the dry-run result so live editing converges. --- locales/index.d.ts | 166 ++++++++++++++++ .../src/pages/admin/mrf-policies.policy.vue | 12 +- .../pages/admin/mrf-policies.reference.vue | 179 ++++++++++++++++++ .../frontend/src/pages/admin/mrf-policies.vue | 7 + sharkey-locales/en-US.yml | 42 ++++ 5 files changed, 399 insertions(+), 7 deletions(-) create mode 100644 packages/frontend/src/pages/admin/mrf-policies.reference.vue diff --git a/locales/index.d.ts b/locales/index.d.ts index 57421fd422..54452b3062 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -12772,6 +12772,172 @@ export interface Locale extends ILocale { * New policy */ "newPolicyName": string; + /** + * Comma-separated. + */ + "commaSeparated": string; + /** + * Syntax reference + */ + "reference": string; + "_reference": { + /** + * Every policy must define a global filter(ctx) function that returns a decision. An optional policy table declares configurable parameters. + */ + "contract": string; + /** + * Example + */ + "example": string; + /** + * Runs in a sandboxed Lua 5.4 VM. os, io, package, require, load and debug are unavailable. Define helpers as local; extra globals trigger persistence warnings. Execution is bounded by the policy timeout. + */ + "sandboxNote": string; + /** + * Context (ctx) + */ + "contextTitle": string; + /** + * The context passed to filter(ctx). + */ + "contextNote": string; + /** + * The raw ActivityPub activity as a table (mutate this for rewrites). + */ + "ctxActivity": string; + /** + * URI of the activity's actor. + */ + "ctxActorUri": string; + /** + * Host of the actor, or nil for local. + */ + "ctxActorHost": string; + /** + * Actor follower count, if known. + */ + "ctxFollowers": string; + /** + * Actor following count, if known. + */ + "ctxFollowing": string; + /** + * This instance's host. + */ + "ctxLocalHost": string; + /** + * Host that signed the request. + */ + "ctxSignerHost": string; + /** + * ISO timestamp of when the activity was received. + */ + "ctxReceivedAt": string; + /** + * Configured parameter values, with schema defaults applied. + */ + "ctxParams": string; + /** + * Decisions + */ + "decisionsTitle": string; + /** + * Accept the activity unchanged. Optional reason. + */ + "decAccept": string; + /** + * Reject the activity. Requires a non-empty reason (logged). + */ + "decReject": string; + /** + * Accept a modified activity. Must be JSON-serializable. + */ + "decRewrite": string; + /** + * Activity helpers + */ + "activityTitle": string; + /** + * Activity type string (e.g. Create, Announce), or nil. + */ + "actType": string; + /** + * The activity's object, or nil. + */ + "actObject": string; + /** + * Actor URI whether actor is a string or object, or nil. + */ + "actActorUri": string; + /** + * The Note object if this is a Create of a Note, else nil. + */ + "actNote": string; + /** + * Note helpers + */ + "noteTitle": string; + /** + * The note's content string, or nil. + */ + "noteContent": string; + /** + * Array of Mention tags on the note. + */ + "noteMentions": string; + /** + * Number of mentions on the note. + */ + "noteMentionCount": string; + /** + * Remove all Mention tags; returns the note. + */ + "noteRemoveMentions": string; + /** + * Mark the note sensitive; sets summary to reason if unset. + */ + "noteMarkSensitive": string; + /** + * Move Public from 'to' into 'cc' (unlists the note). + */ + "noteUnlist": string; + /** + * True if the note has at least one attachment. + */ + "noteHasMedia": string; + /** + * Lookups + */ + "lookupTitle": string; + /** + * Query the local database. These are async and their latency counts against the policy timeout. + */ + "lookupSectionNote": string; + /** + * Fetch a known user by URI, or nil. + */ + "lookupUserUri": string; + /** + * Fetch a known user by mention tag or acct string, or nil. + */ + "lookupUserMention": string; + /** + * Fetch instance metadata by host, or nil. + */ + "lookupInstance": string; + /** + * Fetch a known note by URI, or nil. + */ + "lookupNoteUri": string; + /** + * Utilities + */ + "miscTitle": string; + /** + * True if the value is Lua nil or a JSON null. + */ + "miscIsNil": string; + }; }; "_mfm": { /** diff --git a/packages/frontend/src/pages/admin/mrf-policies.policy.vue b/packages/frontend/src/pages/admin/mrf-policies.policy.vue index 588e5eee1a..daf4d81a9f 100644 --- a/packages/frontend/src/pages/admin/mrf-policies.policy.vue +++ b/packages/frontend/src/pages/admin/mrf-policies.policy.vue @@ -40,6 +40,10 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + @@ -210,12 +214,6 @@ const currentSchema = ref>(initialSchema); const paramEntries = computed(() => Object.entries(currentSchema.value).map(([key, def]) => ({ key, def }))); -function pruneParams(schema: Record) { - for (const key of Object.keys(draft.params)) { - if (!Object.hasOwn(schema, key)) delete draft.params[key]; - } -} - const scopeActivityTypes = ref((props.policy.scope?.activityTypes ?? []).join(', ')); const scopeObjectTypes = ref((props.policy.scope?.objectTypes ?? []).join(', ')); @@ -324,7 +322,7 @@ async function runTest() { // Sync the param form to the schema the tested source actually produced, so editing // the source live and re-testing converges instead of leaving stale param fields. currentSchema.value = { ...(result.paramsSchema ?? {}) as Record }; - pruneParams(currentSchema.value); + draft.params = toEditableParams(currentSchema.value, { ...(result.params ?? {}) }); } catch (err: any) { os.alert({ type: 'error', text: err.message ?? String(err) }); } finally { diff --git a/packages/frontend/src/pages/admin/mrf-policies.reference.vue b/packages/frontend/src/pages/admin/mrf-policies.reference.vue new file mode 100644 index 0000000000..7384bc5e36 --- /dev/null +++ b/packages/frontend/src/pages/admin/mrf-policies.reference.vue @@ -0,0 +1,179 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/mrf-policies.vue b/packages/frontend/src/pages/admin/mrf-policies.vue index 7b48ec19ca..e5aeda0966 100644 --- a/packages/frontend/src/pages/admin/mrf-policies.vue +++ b/packages/frontend/src/pages/admin/mrf-policies.vue @@ -9,6 +9,12 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._mrfPolicies.description }} + + + + + +