Merge commit '5135a036' into remed/lane-S7

This commit is contained in:
Joseph Doherty
2026-10-22 15:46:38 -04:00
117 changed files with 9847 additions and 994 deletions
@@ -21,7 +21,7 @@
<div class="col-md-3">
<label class="form-label">Max concurrent reads</label>
<InputNumber @bind-Value="_form.MaxConcurrentReads" @bind-Value:after="EmitAsync" class="form-control form-control-sm" />
<div class="form-text">Read work items in flight at once. Default 8; 1 = sequential.</div>
<div class="form-text">Read work items in flight at once. Default 8; 1 = sequential (libplctag). The native transport always uses at least 64 — it packs concurrent reads into one request.</div>
</div>
<div class="col-md-3">
<div class="form-check form-switch mt-4">
@@ -15,8 +15,22 @@
</div>
<div class="col-md-3"><label class="form-label">Data type</label>
<select class="form-select form-select-sm" value="@_m.DataType" @onchange="@(e => Update(() => _m.DataType = ParseEnum(e.Value, FocasDataType.Int32)))">
@foreach (var v in Enum.GetValues<FocasDataType>()) { <option value="@v">@v</option> }
</select></div>
@foreach (var v in Enum.GetValues<FocasDataType>())
{
@* String is only readable from the fixed tree (FocasTagDefinitionFactory.DescribeDataTypeMismatch):
offered there, and kept visible when a loaded blob already carries it so the select never lies. *@
if (v != FocasDataType.String || _m.DataType == FocasDataType.String
|| FocasTagDefinitionFactory.DescribeDataTypeMismatch(v, _m.Address) is null)
{
<option value="@v">@v</option>
}
}
</select>
@if (FocasTagDefinitionFactory.DescribeDataTypeMismatch(_m.DataType, _m.Address) is { } typeError)
{
<div class="form-text text-danger small">@typeError</div>
}
</div>
<div class="col-12">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="focas-writable" disabled checked="false" />
@@ -50,7 +50,7 @@ public sealed record ScriptTagInfo(string Path, string Kind, string DataType, st
/// else. Verified chain: <c>AddressSpaceComposer</c> harvests the <c>ctx.GetTag("…")</c> literals
/// (via <c>EquipmentScriptPaths.ExtractDependencyRefs</c>) into <c>DependencyRefs</c>; those become
/// <c>VirtualTagActor._dependencyRefs</c>, registered with <c>DependencyMuxActor</c>, whose
/// <c>_byRef</c> map is keyed (Ordinal) by <c>DriverInstanceActor.AttributeValuePublished.FullReference</c>
/// <c>_byRef</c> map is keyed (Ordinal) by <c>DriverInstanceActor.AttributeValueItem.FullReference</c>
/// — and in v3 a driver's wire reference <b>is the RawPath</b>
/// (<c>DriverHostActor._nodeIdByDriverRef</c> is keyed <c>(DriverInstanceId, RawPath)</c>).
/// </para>
@@ -268,7 +268,16 @@ public sealed class ScriptAnalysisService
var typeInfo = model.GetTypeInfo(memberAccess.Expression);
var type = typeInfo.Type ?? typeInfo.ConvertedType;
if (type == null) return null;
return type.GetMembers()
// The type's own members, then the INSTANCE members it inherits (stopping before System.Object, whose
// ToString/GetHashCode noise was never offered). Without the base chain, a member declared only on
// ScriptContext — ctx.CancellationToken — compiled and published but never completed. Inherited statics
// (ScriptContext.Deadband) are left out: they are not reachable through an instance receiver. This applies to
// EVERY receiver, not only ctx: a struct or enum receiver now also offers the System.ValueType /
// System.Enum instance members it inherits (CompareTo, HasFlag, ...), all of which compile.
var inherited = new List<ISymbol>();
for (var b = type.BaseType; b is not null && b.SpecialType != SpecialType.System_Object; b = b.BaseType)
inherited.AddRange(b.GetMembers().Where(m => !m.IsStatic));
return type.GetMembers().Concat(inherited)
.Where(m => m.CanBeReferencedByName && !m.IsImplicitlyDeclared)
// NotApplicable covers members (e.g. some BCL/special members) that carry no explicit accessibility but are referenceable.
.Where(m => m.DeclaredAccessibility == Accessibility.Public || m.DeclaredAccessibility == Accessibility.NotApplicable)
@@ -80,6 +80,10 @@ public sealed class FocasTagConfigModel
// ahead of the parse consult below and is never replaced by it.
if (FocasAddress.TryParse(Address) is null) return DescribeUnparseableAddress(Address);
// The driver's own address/data-type rule (String only on FIXED: paths) — checked on the typed fields so a
// model built in the editor is judged too, not only a loaded blob the parse gate below can see.
if (FocasTagDefinitionFactory.DescribeDataTypeMismatch(DataType, Address) is { } typeError) return typeError;
// Parse-gate backstop. ⚠️ It gates on the PARSE VERDICT, never on Inspect being non-empty: FOCAS's
// inspector emits advisories for blobs the reader ACCEPTS (an unparseable address, an authored
// 'writable':true on a read-only wire), and promoting those to authoring errors would make the modal