Compare commits

...

2 commits

Author SHA1 Message Date
8cf124530c Harden Control Plane login; fix /ui redirect
Strip whitespace/quotes on pasted tokens; clearer error naming the
OpenBao founding-admin path. Redirect /ui to /ui/. Image 0.1.3.
2026-08-06 21:51:11 +02:00
856dc354d8 Fix Control Plane login under /ui; store founding token in OpenBao
Rewrite templates that lost href/action during root_path rewrite; use
native form controls for reliable login. Deploy image 0.1.2. Founding
admin token canonical location: platform/operators/founding-admin/revenue.
2026-08-06 21:41:59 +02:00
15 changed files with 220 additions and 189 deletions

View file

@ -18,7 +18,7 @@
| workplan | TREV-WP-0008 | finished | — | workplans/TREV-WP-0008-governance-and-pilot-rollout.md |
| workplan | TREV-WP-0009 | finished | — | workplans/TREV-WP-0009-target-revenue-control-plane.md |
| workplan | TREV-WP-0010 | finished | — | workplans/TREV-WP-0010-development-effort-calculator.md |
| workplan | TREV-WP-0011 | active | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| workplan | TREV-WP-0011 | finished | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| workplan | TREV-WP-0012 | finished | — | workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md |
| workplan | TREV-WP-0013 | finished | — | workplans/TREV-WP-0013-remission-credit-automation.md |
| workplan | TREV-WP-0014 | finished | — | workplans/TREV-WP-0014-control-plane-extensions-breach-attestation-ui.md |
@ -90,7 +90,7 @@
| task | TREV-WP-0011-T02 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T03 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T04 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T05 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T05 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0012-T01 | done | — | workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md |
| task | TREV-WP-0012-T02 | done | — | workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md |
| task | TREV-WP-0012-T03 | done | — | workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md |

View file

@ -41,8 +41,19 @@ Coulombcore ArgoCD Application exists for future multi-cluster sync but was
`platform/workloads/target-revenue/runtime-secrets` (signing key, session secret, DSNs).
4. **Image:** `docker build/push …:0.1.1` (0.1.0 had schema-path bug; fixed).
5. **`kubectl apply -k k8s/railiance/`** on railiance01.
6. **Founding token:** bootstrap Job log →
`~/.config/target-revenue/revenue-coulomb-binky.token` (chmod 600; never commit).
6. **Founding token:** bootstrap Job mints `founding-admin`. Canonical copy
lives in OpenBao (never commit):
```text
platform/operators/founding-admin/revenue
fields: token, credential_label, licensor_id, rights, host, ...
```
```bash
bao kv get -mount=platform operators/founding-admin/revenue
```
Optional local cache: `~/.config/target-revenue/revenue-coulomb-binky.token`.
7. **Phase POR:** re-registered
`trsl:phase:info-tech-canon-service-surface` on the public API; local
`trf-pilot-pg` is superseded (ledger was empty, so no entry migration).

View file

@ -30,7 +30,7 @@ spec:
fsGroup: 10001
containers:
- name: bootstrap
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.1
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.3
imagePullPolicy: IfNotPresent
env:
- name: TRF_BOOTSTRAP_BINKY

View file

@ -25,7 +25,7 @@ spec:
fsGroup: 10001
containers:
- name: target-revenue
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.1
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.3
imagePullPolicy: IfNotPresent
ports:
- name: http

View file

@ -28,7 +28,7 @@ spec:
fsGroup: 10001
containers:
- name: migrate
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.1
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.3
imagePullPolicy: IfNotPresent
env:
- name: TRF_RUN_MIGRATIONS

View file

@ -15,6 +15,7 @@ default so a single Service port works without Traefik strip-prefix.
from __future__ import annotations
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from .app import app as trust_service_app
from .control_plane_app import app as control_plane_app
@ -27,6 +28,12 @@ def healthz() -> dict[str, str]:
return {"status": "ok"}
@app.get("/ui")
def ui_no_trailing_slash() -> RedirectResponse:
"""Browsers often hit /ui without a trailing slash; the mount needs /ui/."""
return RedirectResponse(url="/ui/", status_code=307)
# More specific mount first.
app.mount("/ui", control_plane_app)
app.mount("/", trust_service_app)

View file

@ -166,11 +166,22 @@ def login_submit(
token: str = Form(...),
conn: Connection = Depends(get_connection),
):
# OpenBao / password-manager paste often includes trailing newlines or quotes.
cleaned = token.strip().strip('"').strip("'").strip()
if not cleaned:
return _redirect("/login", request, "Credential token is required.", "danger")
try:
registry.authenticate(conn, token)
registry.authenticate(conn, cleaned)
except registry.RegistrationError:
return _redirect("/login", request, "Invalid or revoked credential token.", "danger")
request.session["token"] = token
return _redirect(
"/login",
request,
"Invalid or revoked credential token. Use field `token` from "
"OpenBao path platform/operators/founding-admin/revenue "
"(not a runtime DSN or signing key).",
"danger",
)
request.session["token"] = cleaned
return _redirect("/", request)

View file

@ -13,26 +13,26 @@
{% endif %}
<h3>Issue a new credential</h3>
<form class="wn-form" method="post" >
<wn-field-row label="Credential label (human name)">
<wn-input name="credential_label" required></wn-input>
</wn-field-row>
<wn-field-row label="Rights">
<wn-select name="rights">
<form class="wn-form" method="post" action="{{ root_path }}/admin/credentials">
<label>Credential label (human name)
<input name="credential_label" required>
</label>
<label>Rights
<select name="rights">
<option value="viewer">viewer</option>
<option value="contributor">contributor</option>
<option value="operator">operator</option>
<option value="admin">admin</option>
</wn-select>
</wn-field-row>
<wn-button type="submit" variant="primary">Issue credential</wn-button>
</select>
</label>
<button type="submit">Issue credential</button>
</form>
<h3>Revoke a credential</h3>
<form class="wn-form" method="post" >
<wn-field-row label="Token to revoke">
<wn-input name="token" required></wn-input>
</wn-field-row>
<wn-button type="submit" variant="secondary">Revoke</wn-button>
<form class="wn-form" method="post" action="{{ root_path }}/admin/credentials/revoke">
<label>Token to revoke
<input name="token" required>
</label>
<button type="submit" class="secondary">Revoke</button>
</form>
{% endblock %}

View file

@ -3,8 +3,8 @@
<head>
<meta charset="utf-8">
<title>{% block title %}Target Revenue Control Plane{% endblock %}</title>
<link rel="stylesheet" >
<link rel="stylesheet" >
<link rel="stylesheet" href="{{ root_path }}/static/whynot-design/styles/colors_and_type.css">
<link rel="stylesheet" href="{{ root_path }}/static/whynot-design/styles/components.css">
<script type="importmap">
{
"imports": {
@ -14,8 +14,17 @@
</script>
<script type="module" src="{{ root_path }}/static/whynot-design/index.js"></script>
<style>
body { max-width: 960px; margin: 0 auto; padding: 1.5rem; }
body { max-width: 960px; margin: 0 auto; padding: 1.5rem; font-family: system-ui, sans-serif; }
form.wn-form { display: flex; flex-direction: column; gap: 0.75rem; max-width: 480px; }
form.wn-form label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.9rem; }
form.wn-form input, form.wn-form select, form.wn-form textarea {
padding: 0.5rem 0.6rem; font-size: 1rem; border: 1px solid #ccc; border-radius: 4px;
}
form.wn-form button[type=submit] {
padding: 0.55rem 1rem; font-size: 1rem; cursor: pointer;
background: #1a1a1a; color: #fff; border: none; border-radius: 4px; width: fit-content;
}
form.wn-form button[type=submit].secondary { background: #555; }
.wn-flash { margin: 1rem 0; }
table.wn-plain { width: 100%; border-collapse: collapse; }
table.wn-plain th, table.wn-plain td { text-align: left; padding: 0.4rem 0.6rem; border-bottom: 1px solid #ddd; }
@ -30,12 +39,12 @@
<p>
Signed in as <strong>{{ session_credential_label }}</strong>
({{ session_rights }}) for <strong>{{ session_licensor_id }}</strong>
&middot; <a >Dashboard</a>
&middot; <a >Extensions</a>
{% if session_rights in ("operator", "admin") %}&middot; <a >Proposals</a>{% endif %}
{% if session_rights == "admin" %}&middot; <a >Credentials</a>{% endif %}
&middot; <a >Audit log</a>
&middot; <form method="post" style="display:inline"><button type="submit">Sign out</button></form>
&middot; <a href="{{ root_path }}/">Dashboard</a>
&middot; <a href="{{ root_path }}/extensions">Extensions</a>
{% if session_rights in ("operator", "admin") %}&middot; <a href="{{ root_path }}/proposals">Proposals</a>{% endif %}
{% if session_rights == "admin" %}&middot; <a href="{{ root_path }}/admin/credentials">Credentials</a>{% endif %}
&middot; <a href="{{ root_path }}/audit">Audit log</a>
&middot; <form method="post" action="{{ root_path }}/logout" style="display:inline"><button type="submit">Sign out</button></form>
</p>
{% endif %}
@ -48,25 +57,18 @@
<p style="margin-top:3rem;color:#888;font-size:0.85rem;">
Pilot Control Plane (alpha/beta). First declared Phase:
<code>trsl:phase:info-tech-canon-service-surface</code>
(WP-0008-T05, 2026-08-05). Further Phases require an explicit declaration act.
Visual language vendored from <code>whynot-design</code> — see
<code>static/whynot-design/VENDORED.md</code>.
(WP-0008-T05, 2026-08-05). Live at
<a href="https://revenue.coulomb.social/ui/">revenue.coulomb.social/ui/</a>.
</p>
<script>
// whynot-design's wn-input/wn-select/wn-textarea/wn-button are plain Lit
// components, not form-associated custom elements — their real <input>/
// <select>/<button> live inside shadow DOM, invisible to an ancestor
// <form>. Without this, clicking a wn-button[type=submit] does nothing,
// and even if it did, none of the field values would be part of the POST.
// This bridges both gaps generically, for every .wn-form on every page,
// without touching the vendored library itself.
// Mirror wn-* named fields into real form fields when Lit components are present.
// Native <input>/<button> work without this; this keeps dual support.
(function () {
function readValue(el) {
var native = el.shadowRoot && el.shadowRoot.querySelector("input, select, textarea");
return native ? native.value : (el.value != null ? el.value : "");
}
document.querySelectorAll("form").forEach(function (form) {
form.addEventListener("submit", function () {
form.querySelectorAll("[name]").forEach(function (el) {
@ -84,7 +86,6 @@
hidden.value = readValue(el);
});
});
form.querySelectorAll("wn-button[type=submit]").forEach(function (btn) {
btn.addEventListener("click", function () {
if (typeof form.requestSubmit === "function") form.requestSubmit();

View file

@ -6,7 +6,7 @@
</wn-page-header>
{% if session_rights in ("operator", "admin") %}
<p><a >+ Register a new Phase</a></p>
<p><a href="{{ root_path }}/phases/new">+ Register a new Phase</a></p>
{% endif %}
{% if phases %}
@ -18,7 +18,7 @@
<td>{{ phase.phase.id }}</td>
<td>{{ phase.phase.milestone_release.name }}</td>
<td>{{ phase.phase.initial_target.amount }} {{ phase.phase.initial_target.currency }}</td>
<td><a >View</a></td>
<td><a href="{{ root_path }}/phases/{{ phase.phase.id }}">View</a></td>
</tr>
{% endfor %}
</tbody>

View file

@ -31,10 +31,10 @@
<td>{{ ext.licensor_id }}</td>
<td>
{% if session_rights == "admin" and ext.status == "registered" %}
<form method="post" style="display:inline" class="wn-form">
<form method="post" action="{{ root_path }}/extensions/promote" style="display:inline" class="wn-form">
<input type="hidden" name="extension_id" value="{{ ext.extension_id }}">
<input type="hidden" name="version" value="{{ ext.version }}">
<wn-button type="submit" variant="secondary">Promote to canonical</wn-button>
<button type="submit" class="secondary">Promote to canonical</button>
</form>
{% endif %}
</td>
@ -53,35 +53,35 @@
{% if session_rights in ("operator", "admin") %}
<h3>Register a new extension</h3>
<form class="wn-form" method="post" >
<wn-field-row label="Extension id (trsl:extension:...)">
<wn-input name="extension_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Version (e.g. 1.0)">
<wn-input name="version" required></wn-input>
</wn-field-row>
<wn-field-row label="Value description">
<wn-input name="value_description" required></wn-input>
</wn-field-row>
<wn-field-row label="Pricing method">
<wn-input name="pricing_method" required></wn-input>
</wn-field-row>
<wn-field-row label="Allocation rule">
<wn-input name="allocation_rule" required></wn-input>
</wn-field-row>
<wn-field-row label="Default rate (01, optional)">
<wn-input type="number" step="0.01" min="0" max="1" name="default_rate"></wn-input>
</wn-field-row>
<wn-field-row label="Recognition event">
<wn-input name="recognition_event" value="payment-settled" required></wn-input>
</wn-field-row>
<wn-field-row label="Reversal rule">
<wn-input name="reversal_rule" required></wn-input>
</wn-field-row>
<wn-field-row label="Evidence requirement">
<wn-input name="evidence_requirement" required></wn-input>
</wn-field-row>
<wn-button type="submit" variant="primary">Register extension</wn-button>
<form class="wn-form" method="post" action="{{ root_path }}/extensions">
<label>Extension id (trsl:extension:...)
<input name="extension_id" required>
</label>
<label>Version (e.g. 1.0)
<input name="version" required>
</label>
<label>Value description
<input name="value_description" required>
</label>
<label>Pricing method
<input name="pricing_method" required>
</label>
<label>Allocation rule
<input name="allocation_rule" required>
</label>
<label>Default rate (01, optional)
<input type="number" step="0.01" min="0" max="1" name="default_rate">
</label>
<label>Recognition event
<input name="recognition_event" value="payment-settled" required>
</label>
<label>Reversal rule
<input name="reversal_rule" required>
</label>
<label>Evidence requirement
<input name="evidence_requirement" required>
</label>
<button type="submit">Register extension</button>
</form>
{% endif %}
{% endblock %}

View file

@ -5,10 +5,12 @@
<span slot="title">Sign in</span>
</wn-page-header>
<p>Paste the credential token you were issued by an Admin.</p>
<form class="wn-form" method="post" >
<wn-field-row label="Credential token">
<wn-input type="password" name="token" required></wn-input>
</wn-field-row>
<wn-button type="submit" variant="primary">Sign in</wn-button>
{# Native controls: reliable under /ui mount even if Lit components fail to hydrate. #}
<form class="wn-form" method="post" action="{{ root_path }}/login">
<label>
Credential token
<input type="password" name="token" required autocomplete="current-password">
</label>
<button type="submit">Sign in</button>
</form>
{% endblock %}

View file

@ -11,7 +11,7 @@
<p>
Raw reference: <code>{{ manifest.phase.ledger }}</code>
{% if manifest.phase.ledger.startswith('/phases/') %}
&middot; <a >view live JSON</a>
&middot; <a href="{{ manifest.phase.ledger }}">view live JSON</a>
{% endif %}
</p>
</details>
@ -28,7 +28,7 @@
<td>Degeneration policy</td>
<td>
{{ manifest.phase.degeneration_policy }}
&middot; <a >view spec</a>
&middot; <a href="{{ root_path }}/reference/policies/{{ policy_slug(manifest.phase.degeneration_policy) }}">view spec</a>
</td>
</tr>
<tr><td>Longstop</td><td>{{ metrics.facts.longstop_at or "—" }}</td></tr>
@ -63,8 +63,8 @@
</tr>
</table>
{% if session_rights in ("operator", "admin") %}
<form class="wn-form" method="post" style="margin-bottom:1.5rem;">
<wn-button type="submit" variant="secondary">Apply policy remission now</wn-button>
<form class="wn-form" method="post" action="{{ root_path }}/phases/{{ manifest.phase.id }}/remission" style="margin-bottom:1.5rem;">
<button type="submit" class="secondary">Apply policy remission now</button>
<p style="color:#888;font-size:0.85rem;margin-top:0.5rem;">
Writes a <code>remission-credit</code> delta under <code>system:policy-engine</code>
(idempotent — no double-remit if already current).
@ -90,31 +90,31 @@
<h3>
{% if session_rights == "contributor" %}Propose a Development Credit entry{% else %}Add a Development Credit entry{% endif %}
</h3>
<form class="wn-form" method="post" >
<wn-field-row label="Entry id (trsl:entry:...)">
<wn-input name="entry_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Amount">
<wn-input type="number" step="0.01" name="amount" required></wn-input>
</wn-field-row>
<wn-field-row label="Currency">
<wn-input name="currency" value="{{ manifest.phase.initial_target.currency }}" required></wn-input>
</wn-field-row>
<wn-field-row label="Recognized at (ISO 8601)">
<wn-input name="recognized_at" required></wn-input>
</wn-field-row>
<wn-field-row label="Evidence reference">
<wn-input name="evidence_reference" required></wn-input>
</wn-field-row>
<wn-field-row label="Extension id (trsl:extension:...)">
<wn-input name="extension_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Extension version">
<wn-input name="extension_version" required></wn-input>
</wn-field-row>
<wn-button type="submit" variant="primary">
<form class="wn-form" method="post" action="{{ root_path }}/phases/{{ manifest.phase.id }}/ledger">
<label>Entry id (trsl:entry:...)
<input name="entry_id" required>
</label>
<label>Amount
<input type="number" step="0.01" name="amount" required>
</label>
<label>Currency
<input name="currency" value="{{ manifest.phase.initial_target.currency }}" required>
</label>
<label>Recognized at (ISO 8601)
<input name="recognized_at" required>
</label>
<label>Evidence reference
<input name="evidence_reference" required>
</label>
<label>Extension id (trsl:extension:...)
<input name="extension_id" required>
</label>
<label>Extension version
<input name="extension_version" required>
</label>
<button type="submit">
{% if session_rights == "contributor" %}Submit for review{% else %}Append{% endif %}
</wn-button>
</button>
</form>
{% endif %}
@ -180,35 +180,35 @@
an affirmative CUA authorization check (License V1C1 §7.4) — this form
records that assertion; it does not verify the CUA text.
</p>
<form class="wn-form" method="post" >
<wn-field-row label="Record id (unique)">
<wn-input name="record_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Case id">
<wn-input name="case_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Event type (alleged|cured|determined|terminated)">
<wn-input name="event_type" value="alleged" required></wn-input>
</wn-field-row>
<wn-field-row label="Category">
<wn-input name="category" required></wn-input>
</wn-field-row>
<wn-field-row label="Event at (ISO 8601)">
<wn-input name="event_at" required></wn-input>
</wn-field-row>
<wn-field-row label="Evidence reference (optional)">
<wn-input name="evidence_reference"></wn-input>
</wn-field-row>
<wn-field-row label="Anonymized (true/false)">
<wn-input name="anonymized" value="true" required></wn-input>
</wn-field-row>
<wn-field-row label="Named entitlement holder (only if anonymized=false)">
<wn-input name="named_entitlement_holder"></wn-input>
</wn-field-row>
<wn-field-row label="Named disclosure authorized under CUA (true if named)">
<wn-input name="named_disclosure_authorized" value="false"></wn-input>
</wn-field-row>
<wn-button type="submit" variant="secondary">Publish breach record</wn-button>
<form class="wn-form" method="post" action="{{ root_path }}/phases/{{ manifest.phase.id }}/breach">
<label>Record id (unique)
<input name="record_id" required>
</label>
<label>Case id
<input name="case_id" required>
</label>
<label>Event type (alleged|cured|determined|terminated)
<input name="event_type" value="alleged" required>
</label>
<label>Category
<input name="category" required>
</label>
<label>Event at (ISO 8601)
<input name="event_at" required>
</label>
<label>Evidence reference (optional)
<input name="evidence_reference">
</label>
<label>Anonymized (true/false)
<input name="anonymized" value="true" required>
</label>
<label>Named entitlement holder (only if anonymized=false)
<input name="named_entitlement_holder">
</label>
<label>Named disclosure authorized under CUA (true if named)
<input name="named_disclosure_authorized" value="false">
</label>
<button type="submit" class="secondary">Publish breach record</button>
</form>
{% endif %}
{% endblock %}

View file

@ -6,55 +6,54 @@
</wn-page-header>
<p style="color:#888;">
This registers a real Phase Manifest against the hosted Trust Service.
It does not itself authorize going live for a repo — see
<code>workplans/TREV-WP-0008-governance-and-pilot-rollout.md</code> T05.
Further Phases beyond the T05 pilot still need an explicit declaration act.
</p>
<form class="wn-form" method="post" >
<wn-field-row label="Phase id (trsl:phase:...)">
<wn-input name="phase_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Milestone Release name">
<wn-input name="milestone_release_name" required></wn-input>
</wn-field-row>
<wn-field-row label="Source revision">
<wn-input name="source_revision" required></wn-input>
</wn-field-row>
<wn-field-row label="Repo hub (stable Forgejo instance slug)">
<wn-input name="repo_hub" value="forgejo-coulomb" required></wn-input>
</wn-field-row>
<wn-field-row label="Repo hub URI">
<wn-input name="repo_hub_uri" value="https://forgejo.coulomb.social" required></wn-input>
</wn-field-row>
<wn-field-row label="Repo id (Forgejo's own numeric id)">
<wn-input type="number" name="repo_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Repo name (owner/repo)">
<wn-input name="repo_name" required></wn-input>
</wn-field-row>
<wn-field-row label="Base Phase id (optional — only for a successive Phase)">
<wn-input name="base_phase_id"></wn-input>
</wn-field-row>
<wn-field-row label="Initial Target amount">
<wn-input type="number" step="0.01" name="initial_target_amount" required></wn-input>
</wn-field-row>
<wn-field-row label="Currency (ISO 4217)">
<wn-input name="currency" value="EUR" required></wn-input>
</wn-field-row>
<wn-field-row label="Future License">
<wn-select name="future_license">
<form class="wn-form" method="post" action="{{ root_path }}/phases/new">
<label>Phase id (trsl:phase:...)
<input name="phase_id" required>
</label>
<label>Milestone Release name
<input name="milestone_release_name" required>
</label>
<label>Source revision
<input name="source_revision" required>
</label>
<label>Repo hub (stable Forgejo instance slug)
<input name="repo_hub" value="forgejo-coulomb" required>
</label>
<label>Repo hub URI
<input name="repo_hub_uri" value="https://forgejo.coulomb.social" required>
</label>
<label>Repo id (Forgejo's own numeric id)
<input type="number" name="repo_id" required>
</label>
<label>Repo name (owner/repo)
<input name="repo_name" required>
</label>
<label>Base Phase id (optional — only for a successive Phase)
<input name="base_phase_id">
</label>
<label>Initial Target amount
<input type="number" step="0.01" name="initial_target_amount" required>
</label>
<label>Currency (ISO 4217)
<input name="currency" value="EUR" required>
</label>
<label>Future License
<select name="future_license">
<option value="MIT">MIT</option>
<option value="Apache-2.0">Apache-2.0</option>
</wn-select>
</wn-field-row>
<wn-field-row label="Degeneration policy">
<wn-input name="degeneration_policy" value="trsl:policy:linear-longstop-v0@1.0" required></wn-input>
</wn-field-row>
</select>
</label>
<label>Degeneration policy
<input name="degeneration_policy" value="trsl:policy:linear-longstop-v0@1.0" required>
</label>
<p style="margin-top:-0.5rem;color:#888;font-size:0.85rem;">
<a >view the linear-longstop-v0 spec</a>
<a href="{{ root_path }}/reference/policies/linear-longstop-v0">view the linear-longstop-v0 spec</a>
</p>
<wn-field-row label="Longstop date (ISO 8601)">
<wn-input name="longstop_at" required></wn-input>
</wn-field-row>
<wn-button type="submit" variant="primary">Register Phase</wn-button>
<label>Longstop date (ISO 8601)
<input name="longstop_at" required>
</label>
<button type="submit">Register Phase</button>
</form>
{% endblock %}

View file

@ -16,11 +16,11 @@
<td>{{ p.entry.amount }}</td>
<td>{{ p.created_at }}</td>
<td>
<form method="post" style="display:inline">
<wn-button type="submit" variant="primary">Approve</wn-button>
<form method="post" action="{{ root_path }}/proposals/{{ p.id }}/approve" style="display:inline">
<button type="submit">Approve</button>
</form>
<form method="post" style="display:inline">
<wn-button type="submit" variant="secondary">Reject</wn-button>
<form method="post" action="{{ root_path }}/proposals/{{ p.id }}/reject" style="display:inline">
<button type="submit" class="secondary">Reject</button>
</form>
</td>
</tr>